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