ftp://ftp.smr.ru/pub/0/FreeBSD/releases/distfiles/xscreensaver-3.16.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       for (i = 0; i < 5; i++)   /* if we get all black images, retry up to
322                                    five times. */
323         {
324           VLDevList dl;
325           int j;
326           vlGetDeviceList(server, &dl);
327           vlCloseVideo(server);
328           for (j = 0; j < dl.numDevices; j++)
329             {
330               VLDevice *d = &dl.devices[j];
331               int k;
332               for (k = 0; k < d->numNodes; k++)
333                 if (d->nodes[k].type == VL_SRC &&
334                     d->nodes[k].kind == VL_VIDEO)
335                   if (grab_frame_1(screen, visual, dest, d->nodes[k].number))
336                     return True;
337               /* nothing yet?  go around and try again... */
338             }
339         }
340     }
341 #ifdef DEBUG
342   fprintf (stderr, "%s: images on all video feeds are too dark.\n",
343            progname);
344 #endif /* DEBUG */
345   return False;
346 }
347
348
349 static Bool
350 install_video_frame(unsigned long *image, int width, int height,
351                     Screen *screen, Visual *visual, Drawable dest)
352 {
353   Display *dpy = DisplayOfScreen(screen);
354   int x, y;
355   unsigned int w, h, b, d;
356   Window root;
357   XGCValues gcv;
358   GC gc;
359   XImage *ximage = 0;
360   int image_depth;
361   Bool free_data = False;
362   int vblank_kludge = 3;        /* lose the closed-captioning blips... */
363
364   double gain;
365   char c, *G = get_string_resource("videoGain", "Float");
366   if (!G || (1 != sscanf (G, " %lf %c", &gain, &c)))
367     /* default to the usual NTSC gamma value.  Is this the right thing to do?
368        (Yeah, "gain" isn't quite "gamma", but it's close enough...) */
369     gain = 2.2;
370   if (G) free (G);
371
372   XGetGeometry(dpy, dest, &root, &x, &y, &w, &h, &b, &d);
373   
374   gcv.function = GXcopy;
375   gcv.foreground = BlackPixelOfScreen(screen);
376   gc = XCreateGC (dpy, dest, GCFunction|GCForeground, &gcv);
377
378   image_depth = visual_depth(screen, visual);
379   if (image_depth < 24)
380     image_depth = 24;  /* We'll dither */
381
382   ximage = XCreateImage (dpy, visual, image_depth, ZPixmap, 0, (char *) image,
383                          width, height, 8, 0);
384   XInitImage(ximage);
385   if (!ximage)
386     return False;
387
388   if (gain > 0.0)       /* Pump up the volume */
389     {
390       unsigned char *end = (unsigned char *) (image + (width * height));
391       unsigned char *s = (unsigned char *) image;
392       while (s < end)
393         {
394           unsigned int r = s[1] * gain;
395           unsigned int g = s[2] * gain;
396           unsigned int b = s[3] * gain;
397           s[1] = (r > 255 ? 255 : r);
398           s[2] = (g > 255 ? 255 : g);
399           s[3] = (b > 255 ? 255 : b);
400           s += 4;
401         }
402     }
403
404   /* If the drawable is not of truecolor depth, we need to convert the
405      grabbed bits to match the depth by clipping off the less significant
406      bit-planes of each color component.
407   */
408   if (d != 24 && d != 32)
409     {
410       int x, y;
411       /* We use the same ximage->data in both images -- that's ok, because
412          since we're reading from B and writing to A, and B uses more bytes
413          per pixel than A, the write pointer won't overrun the read pointer.
414       */
415       XImage *ximage2 = XCreateImage (dpy, visual, d, ZPixmap, 0,
416                                       (char *) image,
417                                       width, height, 8, 0);
418       XInitImage(ximage2);
419       if (!ximage2)
420         {
421           XDestroyImage(ximage);
422           return False;
423         }
424
425 #ifdef DEBUG
426       fprintf(stderr, "%s: converting from depth %d to depth %d\n",
427               progname, ximage->depth, ximage2->depth);
428 #endif /* DEBUG */
429
430       for (y = 0; y < ximage->height; y++)
431         for (x = 0; x < ximage->width; x++)
432           {
433             unsigned long pixel = XGetPixel(ximage, x, y);
434             unsigned int r = (pixel & 0x0000FF);
435             unsigned int g = (pixel & 0x00FF00) >> 8;
436             unsigned int b = (pixel & 0xFF0000) >> 16;
437
438             if (d == 8)
439               pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
440             else if (d == 12)
441               pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
442             else if (d == 16)
443               pixel = ((r >> 3) | ((g >> 3) << 5) | ((b >> 3) << 10));
444             else
445               abort();
446
447             XPutPixel(ximage2, x, y, pixel);
448           }
449       ximage->data = 0;
450       XDestroyImage(ximage);
451       ximage = ximage2;
452     }
453
454   if (width < w && height < h)      /* Stretch the image to fit the window. */
455     {
456       double dw = (((double) w) / ((double) width));
457       double dh = (((double) h) / ((double) height));
458       double d = (dw > dh ? dh : dw);
459       int width2  = d * width;
460       int height2 = d * height;
461       int x, y;
462       XImage *ximage2 = XCreateImage (dpy, visual, ximage->depth, ZPixmap,
463                                       0, NULL,
464                                       width2, height2, 8, 0);
465       if (!ximage2->data)
466         ximage2->data = (char *) malloc(width2 * height2 * 4);
467       free_data = True;
468       XInitImage(ximage2);
469 #ifdef DEBUG
470       fprintf(stderr, "%s: stretching video image by %f (%d %d -> %d %d)\n",
471               progname, d, width, height, width2, height2);
472 #endif /* DEBUG */
473       for (y = 0; y < height2; y++)
474         {
475           int y2 = (int) (y / d);
476           for (x = 0; x < width2; x++)
477             XPutPixel(ximage2, x, y, XGetPixel(ximage, (int) (x / d), y2));
478         }
479       ximage->data = 0;
480       XDestroyImage(ximage);
481       ximage = ximage2;
482       width = width2;
483       height = height2;
484       vblank_kludge *= d;
485     }
486
487   XFillRectangle(dpy, dest, gc, 0, 0, w, h);
488   XPutImage(dpy, dest, gc, ximage, 0, vblank_kludge,
489             (w - width) / 2,
490             (h - height) / 2,
491             width, height - vblank_kludge);
492   XSync(dpy, False);
493
494   if (free_data)
495     free(ximage->data);
496   ximage->data = 0;
497   XDestroyImage(ximage);
498   XFreeGC (dpy, gc);
499   return True;
500 }
501
502 #endif /* HAVE_SGI_VIDEO */