c636754d88560e1c8bf0904c33d75f7fb38cdbfc
[xscreensaver] / OSX / osxgrabscreen.m
1 /* xscreensaver, Copyright (c) 1992-2009 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 is the OSX implementation of desktop-grabbing and image-loading.
13  */
14
15 #import <stdlib.h>
16 #import <stdint.h>
17 #import <Cocoa/Cocoa.h>
18 #import "jwxyz.h"
19 #import "grabscreen.h"
20 #import "colorbars.h"
21 #import "resources.h"
22 #import "usleep.h"
23
24
25 static void
26 copy_framebuffer_to_ximage (CGDirectDisplayID cgdpy, XImage *xim,
27                             int window_x, int window_y)
28 {
29   unsigned char *data = (unsigned char *) 
30     CGDisplayAddressForPosition (cgdpy, window_x, window_y);
31   int bpp = CGDisplayBitsPerPixel (cgdpy);
32   int spp = CGDisplaySamplesPerPixel (cgdpy);
33   int bps = CGDisplayBitsPerSample (cgdpy);
34   int bpr = CGDisplayBytesPerRow (cgdpy);
35
36   int y;
37   int ximw = xim->width;
38   int ximh = xim->height;
39
40   uint32_t *odata = (uint32_t *) xim->data;
41
42   switch (bpp) {
43   case 32:
44     if (spp != 3) abort();
45     if (bps != 8) abort();
46     int xwpl = xim->bytes_per_line/4;
47     for (y = 0; y < ximh; y++) {
48       // We can do this because the frame buffer and XImage are both ARGB 32.
49       // Both PPC and Intel use ARGB, viewed in word order (not byte-order).
50       memcpy (odata, data, ximw * 4);
51       odata += xwpl;
52       data += bpr;
53     }
54     break;
55
56   case 16:
57     if (spp != 3) abort();
58     if (bps != 5) abort();
59     for (y = 0; y < ximh; y++) {
60       uint16_t *ip = (uint16_t *) data;
61       int x;
62       for (x = 0; x < ximw; x++) {
63         uint16_t p = *ip++;
64         // This should be ok on both PPC and Intel (ARGB, word order)
65         unsigned char r = (p >> 10) & 0x1F;
66         unsigned char g = (p >>  5) & 0x1F;
67         unsigned char b = (p      ) & 0x1F;
68         r = (r << 3) | (r >> 2);
69         g = (g << 3) | (g >> 2);
70         b = (b << 3) | (b >> 2);
71         uint32_t pixel = 0xFF000000 | (r << 16) | (g << 8) | b;
72         // XPutPixel (xim, x, y, pixel);
73         *odata++ = pixel;
74       }
75       data += bpr;
76     }
77     break;
78
79   case 8:
80     {
81       /* Get the current palette of the display. */
82       CGDirectPaletteRef pal = CGPaletteCreateWithDisplay (cgdpy);
83
84       /* Map it to 32bpp pixels */
85       uint32_t map[256];
86       for (y = 0; y < 256; y++) {
87         CGDeviceColor c = CGPaletteGetColorAtIndex (pal, y);
88         unsigned char r = c.red   * 255.0;
89         unsigned char g = c.green * 255.0;
90         unsigned char b = c.blue  * 255.0;
91         uint32_t pixel = 0xFF000000 | (r << 16) | (g << 8) | b;
92         map[y] = pixel;
93       }
94
95       for (y = 0; y < ximh; y++) {
96         unsigned char *ip = data;
97         int x;
98         for (x = 0; x < ximw; x++) {
99           *odata++ = map[*ip++];
100         }
101         data += bpr;
102       }
103       CGPaletteRelease (pal);
104     }
105     break;
106
107   default:
108     abort();
109     break;
110   }
111 }
112
113
114 /* Loads an image into the Drawable, returning once the image is loaded.
115  */
116 void
117 osx_grab_desktop_image (Screen *screen, Window xwindow, Drawable drawable)
118 {
119   Display *dpy = DisplayOfScreen (screen);
120   XWindowAttributes xgwa;
121   NSView *nsview = jwxyz_window_view (xwindow);
122   NSWindow *nswindow = [nsview window];
123   int window_x, window_y;
124   Window unused;
125
126   // figure out where this window is on the screen
127   //
128   XGetWindowAttributes (dpy, xwindow, &xgwa);
129   XTranslateCoordinates (dpy, xwindow, RootWindowOfScreen (screen), 0, 0, 
130                          &window_x, &window_y, &unused);
131   
132   // Use the size of the Drawable, not the Window.
133   {
134     Window r;
135     int x, y;
136     unsigned int w, h, bbw, d;
137     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
138     xgwa.width = w;
139     xgwa.height = h;
140   }
141
142   // Create a tmp ximage to hold the screen data.
143   //
144   XImage *xim = XCreateImage (dpy, xgwa.visual, 32, ZPixmap, 0, 0,
145                               xgwa.width, xgwa.height, 8, 0);
146   xim->data = (char *) malloc (xim->height * xim->bytes_per_line);
147
148
149   // Find the address in the frame buffer of the top left of this window.
150   //
151   CGDirectDisplayID cgdpy = 0;
152   {
153     CGPoint p;
154     // #### this isn't quite right for screen 2: it's offset slightly.
155     p.x = window_x;
156     p.y = window_y;
157     CGDisplayCount n;
158     CGGetDisplaysWithPoint (p, 1, &cgdpy, &n);
159     if (!cgdpy) abort();
160   }
161
162   // Paint a transparent "hole" in this window.
163   //
164   BOOL oopaque = [nswindow isOpaque];
165   [nswindow setOpaque:NO];
166
167   [[NSColor clearColor] set];
168   NSRectFill ([nsview frame]);
169   [[nswindow graphicsContext] flushGraphics];
170
171
172   // Without this, we get a dozen black scanlines at the top.
173   // #### But with this, the screen saver loops, because calling this
174   //      seems to implicitly mark the display as non-idle!
175   // CGDisplayCaptureWithOptions (cgdpy, kCGCaptureNoFill);
176
177   // #### So let's try waiting for the vertical blank instead...
178   //      Nope, that doesn't work.
179   //
180   // CGDisplayWaitForBeamPositionOutsideLines (cgdpy, 0,
181   //   window_y + [nswindow frame].size.height);
182
183   // #### Ok, try a busy-wait?
184   //      Nope.
185   //
186
187   // #### Ok, just fuckin' sleep!
188   //
189   usleep (100000);
190
191
192   // Pull the bits out of the frame buffer.
193   //
194   copy_framebuffer_to_ximage (cgdpy, xim, window_x, window_y);
195
196   // CGDisplayRelease (cgdpy);
197
198   // Make the window visible again.
199   //
200   [nswindow setOpaque:oopaque];
201
202   // Splat the XImage onto the target drawable (probably the window)
203   // and free the bits.
204   //
205   GC gc = 0;
206   XPutImage (dpy, drawable, gc, xim, 0, 0, 0, 0, xim->width, xim->height);
207   XDestroyImage (xim);
208 }
209
210
211 /* Returns the EXIF rotation property of the image, if any.
212  */
213 static int
214 exif_rotation (const char *filename)
215 {
216   /* This is a ridiculous amount of rigamarole to go through, but for some
217      reason the "Orientation" tag does not exist in the "NSImageEXIFData"
218      dictionary inside the NSBitmapImageRep of the NSImage.  Several other
219      EXIF tags are there (e.g., shutter speed) but not orientation.  WTF?
220    */
221   CFStringRef s = CFStringCreateWithCString (NULL, filename, 
222                                              kCFStringEncodingUTF8);
223   CFURLRef url = CFURLCreateWithFileSystemPath (NULL, s, 
224                                                 kCFURLPOSIXPathStyle, 0);
225   CGImageSourceRef cgimg = CGImageSourceCreateWithURL (url, NULL);
226   if (! cgimg) return -1;
227
228   NSDictionary *props = (NSDictionary *)
229     CGImageSourceCopyPropertiesAtIndex (cgimg, 0, NULL);
230   int rot = [[props objectForKey:@"Orientation"] intValue];
231   CFRelease (cgimg);
232   CFRelease (url);
233   CFRelease (s);
234   return rot;
235 }
236
237
238 /* Loads an image file and splats it onto the drawable.
239    The image is drawn as large as possible while preserving its aspect ratio.
240    If geom_ret is provided, the actual rectangle the rendered image takes
241    up will be returned there.
242  */
243 Bool
244 osx_load_image_file (Screen *screen, Window xwindow, Drawable drawable,
245                      const char *filename, XRectangle *geom_ret)
246 {
247   NSImage *img = [[NSImage alloc] initWithContentsOfFile:
248                                     [NSString stringWithCString:filename
249                                               encoding:NSUTF8StringEncoding]];
250   if (!img)
251     return False;
252
253   jwxyz_draw_NSImage (DisplayOfScreen (screen), drawable, img, geom_ret,
254                       exif_rotation (filename));
255   [img release];
256   return True;
257 }
258