http://ftp.x.org/contrib/applications/xscreensaver-3.26.tar.gz
[xscreensaver] / utils / sgivideo.c
1 /* xscreensaver, Copyright (c) 1997, 1998 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
12 /* This file contains code for grabbing a frame from one of the video inputs
13    on an SGI.  It returns it on a Drawable where it can be hacked at will.
14    This code checks all available video devices for the first one with a
15    non-blank signal.
16
17    It checks the deviced whose number comes from the `videoDevice' resource
18    first, then the default video device, then all the devices in order.
19
20    The intensity of the video signal is increased by the value of the
21    `videoGain' resource (a float) defaulting to 2.2, since NTSC video tends
22    to appear kind of dim on computer screens.
23
24    The video image is expanded to fit the window (while preserving the aspect
25    ratio.)  This is done by simply replicating pixels, not dithering.  That
26    turns out to look good enough most of the time.
27
28    If the target window is not TrueColor, the grabbed image will be quantized
29    to fit.  This also is done without dithering, but in this case, dithering
30    would help a lot, because it looks like crap.  So use TrueColor if you care.
31  */
32
33 #include "utils.h"
34 #include "sgivideo.h"
35 #include "resources.h"
36 #include "visual.h"
37
38 #ifdef HAVE_SGI_VIDEO   /* whole file */
39
40 #include "usleep.h"
41
42 #include <X11/Xutil.h>
43
44 #ifdef DEBUG
45 extern char *progname;
46 #endif /* DEBUG */
47
48
49 # include <dmedia/vl.h>
50
51 static Bool dark_image_p(unsigned long *image, int width, int height);
52 static Bool install_video_frame(unsigned long *image, int width, int height,
53                                 Screen *screen, Visual *visual, Drawable dest);
54
55 #ifdef DEBUG
56 static void
57 describe_input(const char *prefix, VLServer server, int camera)
58 {
59   VLDevList dl;
60   int i, j;
61
62   if (camera == VL_ANY)
63     {
64       printf("%s: %s VL_ANY\n", progname, prefix);
65       return;
66     }
67
68   vlGetDeviceList(server, &dl);
69   for (i = 0; i < dl.numDevices; i++)
70     {
71       VLDevice *d = &dl.devices[i];
72       for (j = 0; j < d->numNodes; j++)
73         if (d->nodes[j].number == camera)
74           {
75             printf("%s: %s %d, \"%s\"\n", progname, prefix,
76                    d->nodes[j].number,
77                    d->nodes[j].name);
78             return;
79           }
80     }
81
82   /* else... */
83   printf("%s: %s %d (???)\n", progname, prefix, camera);
84 }
85 #endif /* DEBUG */
86
87
88 static Bool
89 grab_frame_1(Screen *screen, Visual *visual, Drawable dest, int camera)
90 {
91   Bool status = False;
92   int width = 0;
93   int height = 0;
94   VLServer server = 0;
95   VLNode input = -1;
96   VLNode output = -1;
97   VLPath path = 0;
98   VLBuffer buffer = 0;
99   VLControlValue ctl;
100   VLInfoPtr info;
101   VLTransferDescriptor trans;
102   unsigned long *image = 0;
103
104   server = vlOpenVideo (NULL);
105   if (!server)
106     {
107 #ifdef DEBUG
108       fprintf (stderr, "%s: unable to open video server\n", progname);
109 #endif /* DEBUG */
110       goto DONE;
111     }
112
113 #ifdef DEBUG
114   describe_input("trying device", server, camera);
115 #endif /* DEBUG */
116
117   input  = vlGetNode (server, VL_SRC, VL_VIDEO, camera);
118   output = vlGetNode (server, VL_DRN, VL_MEM, VL_ANY);
119
120   if (input == -1 || output == -1)
121     {
122 #ifdef DEBUG
123       fprintf (stderr, "%s: unable to get video I/O nodes: %d\n",
124                progname, vlErrno);
125 #endif /* DEBUG */
126       goto DONE;
127     }
128
129   path = vlCreatePath (server, VL_ANY, input, output);
130   if (path == -1)
131     {
132 #ifdef DEBUG
133       fprintf (stderr, "%s: unable to get video path: %d\n",
134                progname, vlErrno);
135 #endif /* DEBUG */
136       goto DONE;
137     }
138
139   if (vlSetupPaths (server, (VLPathList) &path, 1, VL_SHARE, VL_SHARE) == -1)
140     {
141 #ifdef DEBUG
142       fprintf (stderr, "%s: unable to set up video path: %d\n",
143                progname, vlErrno);
144 #endif /* DEBUG */
145       goto DONE;
146     }
147
148   ctl.intVal = VL_CAPTURE_INTERLEAVED;
149   if (vlSetControl (server, path, output, VL_CAP_TYPE, &ctl) == -1)
150     {
151 #ifdef DEBUG
152       fprintf (stderr,
153                "%s: unable to set video capture type to interleaved: %d\n",
154                progname, vlErrno);
155 #endif /* DEBUG */
156       goto DONE;
157     }
158
159   ctl.intVal = VL_PACKING_RGBA_8;
160   if (vlSetControl (server, path, output, VL_PACKING, &ctl) == -1)
161     {
162 #ifdef DEBUG
163       fprintf (stderr, "%s: unable to set video packing to RGB8: %d\n",
164                progname, vlErrno);
165 #endif /* DEBUG */
166       goto DONE;
167     }
168
169   buffer = vlCreateBuffer (server, path, output, 3);
170   if (!buffer)
171     {
172 #ifdef DEBUG
173       fprintf (stderr, "%s: unable to create video buffer: %d\n",
174                progname, vlErrno);
175 #endif /* DEBUG */
176       goto DONE;
177     }
178
179   vlRegisterBuffer (server, path, output, buffer);
180
181   memset(&trans, 0, sizeof(trans));
182   trans.trigger = VLTriggerImmediate;
183   trans.mode = VL_TRANSFER_MODE_DISCRETE;
184   trans.delay = 0;
185   trans.count = 1;
186   if (vlBeginTransfer (server, path, 1, &trans) == -1)
187     {
188 #ifdef DEBUG
189       fprintf (stderr, "%s: unable to begin video transfer: %d\n",
190                progname, vlErrno);
191 #endif /* DEBUG */
192       goto DONE;
193     }
194     
195   
196   /* try to get a frame; don't try more than a zillion times.
197      I strongly suspect this isn't the best way to do this...
198    */
199   {
200     int i;
201     for (i = 0; i < 1000; i++)
202       {
203         info = vlGetLatestValid (server, buffer);
204         if (info) break;
205         usleep(10000);  /* 1/100th second (a bit more than half a field) */
206       }
207   }
208
209   if (!info)
210     {
211 #ifdef DEBUG
212       fprintf (stderr, "%s: unable to get video info: %d\n",
213                progname, vlErrno);
214 #endif /* DEBUG */
215       goto DONE;
216     }
217
218   image = vlGetActiveRegion (server, buffer, info);
219   if (!image)
220     {
221 #ifdef DEBUG
222       fprintf (stderr, "%s: unable to grab video frame: %d\n",
223                progname, vlErrno);
224 #endif /* DEBUG */
225       goto DONE;
226     }
227
228   if (vlGetControl (server, path, input, VL_SIZE, &ctl) != -1)
229     {
230       width  = ctl.xyVal.x;
231       height = ctl.xyVal.y;
232     }
233   else
234     {
235 #ifdef DEBUG
236       fprintf (stderr, "%s: unable to get video image size: %d\n",
237                progname, vlErrno);
238 #endif /* DEBUG */
239       goto DONE;
240     }
241
242 #ifdef DEBUG
243   describe_input("read device", server, camera);
244 #endif /* DEBUG */
245
246   if (dark_image_p(image, width, height))
247     goto DONE;
248
249   status = install_video_frame(image, width, height, screen, visual, dest);
250
251  DONE:
252
253   /* I think `image' is freed as a result of destroying buffer. */
254
255   if (buffer)
256     vlDestroyBuffer(server, buffer);
257   if (path)
258     vlDestroyPath(server, path);
259   if (server)
260     vlCloseVideo (server);
261
262   return status;
263 }
264
265
266 static Bool
267 dark_image_p(unsigned long *image, int width, int height)
268 {
269   double max = 0.02;
270   double total = 0.0;
271   int i;
272   int pixels = (width * height);
273 #ifdef DEBUG
274   int mr = 0, mg = 0, mb = 0;
275 #endif /* DEBUG */
276   for (i = pixels-1; i >= 0; i--)
277     {
278       unsigned long pixel = image[i];
279       unsigned int r = (pixel & 0x0000FF);
280       unsigned int g = (pixel & 0x00FF00) >> 8;
281       unsigned int b = (pixel & 0xFF0000) >> 16;
282 #ifdef DEBUG
283       if (r > mr) mr = r;
284       if (g > mg) mg = g;
285       if (b > mb) mb = b;
286 #endif /* DEBUG */
287       total += ((r * (0.3086 / 0xFF)) +       /* gamma 1.0 intensity values */
288                 (g * (0.6094 / 0xFF)) +
289                 (b * (0.0820 / 0xFF)));
290     }
291   total /= pixels;
292 #ifdef DEBUG
293   fprintf(stderr, "%s: %sdark %f (max rgb: %d %d %d)\n", progname,
294           (total < max ? "" : "not "),
295           total, mr, mg, mb);
296 #endif /* DEBUG */
297   return (total < max);
298 }
299
300
301 Bool
302 grab_video_frame(Screen *screen, Visual *visual, Drawable dest)
303 {
304   char *def_camera = get_string_resource("videoDevice", "Integer");
305   if (def_camera && *def_camera)
306     {
307       int cam;
308       char c;
309       int ok = (1 == sscanf(def_camera, " %d %c", &cam, &c));
310       free (def_camera);
311       if (ok && grab_frame_1(screen, visual, dest, cam))
312         return True;
313     }
314
315   if (grab_frame_1(screen, visual, dest, VL_ANY))
316     return True;
317   else
318     {
319       int i;
320       VLServer server = vlOpenVideo (NULL);
321
322       if (!server) return False;
323
324       for (i = 0; i < 5; i++)   /* if we get all black images, retry up to
325                                    five times. */
326         {
327           VLDevList dl;
328           int j;
329
330           j = vlGetDeviceList(server, &dl);
331           vlCloseVideo(server);
332           if (j < 0) return False;
333
334           for (j = 0; j < dl.numDevices; j++)
335             {
336               VLDevice *d = &dl.devices[j];
337               int k;
338               for (k = 0; k < d->numNodes; k++)
339                 if (d->nodes[k].type == VL_SRC &&
340                     d->nodes[k].kind == VL_VIDEO)
341                   if (grab_frame_1(screen, visual, dest, d->nodes[k].number))
342                     return True;
343               /* nothing yet?  go around and try again... */
344             }
345         }
346     }
347 #ifdef DEBUG
348   fprintf (stderr, "%s: images on all video feeds are too dark.\n",
349            progname);
350 #endif /* DEBUG */
351   return False;
352 }
353
354
355 static Bool
356 install_video_frame(unsigned long *image, int width, int height,
357                     Screen *screen, Visual *visual, Drawable dest)
358 {
359   Display *dpy = DisplayOfScreen(screen);
360   int x, y;
361   unsigned int w, h, b, d;
362   Window root;
363   XGCValues gcv;
364   GC gc;
365   XImage *ximage = 0;
366   int image_depth;
367   Bool free_data = False;
368   int vblank_kludge = 3;        /* lose the closed-captioning blips... */
369
370   double gain;
371   char c, *G = get_string_resource("videoGain", "Float");
372   if (!G || (1 != sscanf (G, " %lf %c", &gain, &c)))
373     /* default to the usual NTSC gamma value.  Is this the right thing to do?
374        (Yeah, "gain" isn't quite "gamma", but it's close enough...) */
375     gain = 2.2;
376   if (G) free (G);
377
378   XGetGeometry(dpy, dest, &root, &x, &y, &w, &h, &b, &d);
379   
380   gcv.function = GXcopy;
381   gcv.foreground = BlackPixelOfScreen(screen);
382   gc = XCreateGC (dpy, dest, GCFunction|GCForeground, &gcv);
383
384   image_depth = visual_depth(screen, visual);
385   if (image_depth < 24)
386     image_depth = 24;  /* We'll dither */
387
388   ximage = XCreateImage (dpy, visual, image_depth, ZPixmap, 0, (char *) image,
389                          width, height, 8, 0);
390   XInitImage(ximage);
391   if (!ximage)
392     return False;
393
394   if (gain > 0.0)       /* Pump up the volume */
395     {
396       unsigned char *end = (unsigned char *) (image + (width * height));
397       unsigned char *s = (unsigned char *) image;
398       while (s < end)
399         {
400           unsigned int r = s[1] * gain;
401           unsigned int g = s[2] * gain;
402           unsigned int b = s[3] * gain;
403           s[1] = (r > 255 ? 255 : r);
404           s[2] = (g > 255 ? 255 : g);
405           s[3] = (b > 255 ? 255 : b);
406           s += 4;
407         }
408     }
409
410   /* If the drawable is not of truecolor depth, we need to convert the
411      grabbed bits to match the depth by clipping off the less significant
412      bit-planes of each color component.
413   */
414   if (d != 24 && d != 32)
415     {
416       int x, y;
417       /* We use the same ximage->data in both images -- that's ok, because
418          since we're reading from B and writing to A, and B uses more bytes
419          per pixel than A, the write pointer won't overrun the read pointer.
420       */
421       XImage *ximage2 = XCreateImage (dpy, visual, d, ZPixmap, 0,
422                                       (char *) image,
423                                       width, height, 8, 0);
424       XInitImage(ximage2);
425       if (!ximage2)
426         {
427           XDestroyImage(ximage);
428           return False;
429         }
430
431 #ifdef DEBUG
432       fprintf(stderr, "%s: converting from depth %d to depth %d\n",
433               progname, ximage->depth, ximage2->depth);
434 #endif /* DEBUG */
435
436       for (y = 0; y < ximage->height; y++)
437         for (x = 0; x < ximage->width; x++)
438           {
439             unsigned long pixel = XGetPixel(ximage, x, y);
440             unsigned int r = (pixel & 0x0000FF);
441             unsigned int g = (pixel & 0x00FF00) >> 8;
442             unsigned int b = (pixel & 0xFF0000) >> 16;
443
444             if (d == 8)
445               pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
446             else if (d == 12)
447               pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
448             else if (d == 16)
449               pixel = ((r >> 3) | ((g >> 3) << 5) | ((b >> 3) << 10));
450             else
451               abort();
452
453             XPutPixel(ximage2, x, y, pixel);
454           }
455       ximage->data = 0;
456       XDestroyImage(ximage);
457       ximage = ximage2;
458     }
459
460   if (width < w && height < h)      /* Stretch the image to fit the window. */
461     {
462       double dw = (((double) w) / ((double) width));
463       double dh = (((double) h) / ((double) height));
464       double d = (dw > dh ? dh : dw);
465       int width2  = d * width;
466       int height2 = d * height;
467       int x, y;
468       XImage *ximage2 = XCreateImage (dpy, visual, ximage->depth, ZPixmap,
469                                       0, NULL,
470                                       width2, height2, 8, 0);
471       if (!ximage2->data)
472         ximage2->data = (char *) malloc(width2 * height2 * 4);
473       free_data = True;
474       XInitImage(ximage2);
475 #ifdef DEBUG
476       fprintf(stderr, "%s: stretching video image by %f (%d %d -> %d %d)\n",
477               progname, d, width, height, width2, height2);
478 #endif /* DEBUG */
479       for (y = 0; y < height2; y++)
480         {
481           int y2 = (int) (y / d);
482           for (x = 0; x < width2; x++)
483             XPutPixel(ximage2, x, y, XGetPixel(ximage, (int) (x / d), y2));
484         }
485       ximage->data = 0;
486       XDestroyImage(ximage);
487       ximage = ximage2;
488       width = width2;
489       height = height2;
490       vblank_kludge *= d;
491     }
492
493   XFillRectangle(dpy, dest, gc, 0, 0, w, h);
494   XPutImage(dpy, dest, gc, ximage, 0, vblank_kludge,
495             (w - width) / 2,
496             (h - height) / 2,
497             width, height - vblank_kludge);
498   XSync(dpy, False);
499
500   if (free_data)
501     free(ximage->data);
502   ximage->data = 0;
503   XDestroyImage(ximage);
504   XFreeGC (dpy, gc);
505   return True;
506 }
507
508 #endif /* HAVE_SGI_VIDEO */