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