http://www.jwz.org/xscreensaver/xscreensaver-5.12.tar.gz
[xscreensaver] / OSX / jwxyz.m
index b7fc19fe47d493bf15598d4e104ff637ba6f17f2..604caf50dafb066ea1689f4172250918790aed2d 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1991-2006 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1991-2010 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
@@ -17,6 +17,7 @@
  */
 
 #import <stdlib.h>
+#import <stdint.h>
 #import <Cocoa/Cocoa.h>
 #import "jwxyz.h"
 #import "jwxyz-timers.h"
@@ -37,6 +38,7 @@
 struct jwxyz_Drawable {
   enum { WINDOW, PIXMAP } type;
   CGContextRef cgc;
+  CGImageRef cgi;
   CGRect frame;
   union {
     struct {
@@ -45,6 +47,7 @@ struct jwxyz_Drawable {
     } window;
     struct {
       int depth;
+      void *cgc_buffer;                // the bits to which CGContextRef renders
     } pixmap;
   };
 };
@@ -112,6 +115,7 @@ jwxyz_make_display (void *nsview_arg)
   // w->cgc = [[[view window] graphicsContext] graphicsPort];
   w->cgc = 0;
   w->window.view = view;
+  CFRetain (w->window.view);   // needed for garbage collection?
   w->window.background = BlackPixel(0,0);
 
   d->main_window = w;
@@ -145,6 +149,21 @@ jwxyz_window_view (Window w)
   return w->window.view;
 }
 
+
+/* Call this after any modification to the bits on a Pixmap or Window.
+   Most Pixmaps are used frequently as sources and infrequently as
+   destinations, so it pays to cache the data as a CGImage as needed.
+ */
+static void
+invalidate_drawable_cache (Drawable d)
+{
+  if (d && d->cgi) {
+    CGImageRelease (d->cgi);
+    d->cgi = 0;
+  }
+}
+
+
 /* Call this when the View changes size or position.
  */
 void
@@ -186,6 +205,8 @@ jwxyz_window_resized (Display *dpy, Window w)
   //
   dpy->colorspace = CGColorSpaceCreateDeviceRGB();
 # endif
+
+  invalidate_drawable_cache (w);
 }
 
 
@@ -244,7 +265,6 @@ XDisplayHeight (Display *dpy, int screen)
   return (int) dpy->main_window->frame.size.height;
 }
 
-
 static void
 validate_pixel (unsigned long pixel, unsigned int depth, BOOL alpha_allowed_p)
 {
@@ -382,7 +402,7 @@ push_bg_gc (Drawable d, GC gc, Bool fill_p)
 
    It is *way* faster to draw points by creating and drawing a 1x1 CGImage
    with repeated calls to CGContextDrawImage than it is to make a single
-   call to CGContextFillRects()!
+   call to CGContextFillRects() with a list of 1x1 rectangles!
 
    I still wouldn't call it *fast*, however...
  */
@@ -395,6 +415,8 @@ XDrawPoints (Display *dpy, Drawable d, GC gc,
   int i;
   CGRect wr = d->frame;
 
+  push_fg_gc (d, gc, YES);
+
 # ifdef XDRAWPOINTS_IMAGES
 
   unsigned int argb = gc->gcv.foreground;
@@ -440,7 +462,6 @@ XDrawPoints (Display *dpy, Drawable d, GC gc,
   CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
   CGRect *r = rects;
   
-  push_fg_gc (d, gc, YES);
   for (i = 0; i < count; i++) {
     r->size.width = r->size.height = 1;
     if (i > 0 && mode == CoordModePrevious) {
@@ -459,6 +480,9 @@ XDrawPoints (Display *dpy, Drawable d, GC gc,
 
 # endif /* ! XDRAWPOINTS_IMAGES */
 
+  pop_gc (d, gc);
+  invalidate_drawable_cache (d);
+
   return 0;
 }
 
@@ -483,6 +507,7 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
            unsigned int width, unsigned int height, 
            int dst_x, int dst_y)
 {
+  Assert (gc, "no GC");
   Assert ((width  < 65535), "improbably large width");
   Assert ((height < 65535), "improbably large height");
   Assert ((src_x  < 65535 && src_x  > -65535), "improbably large src_x");
@@ -493,8 +518,8 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
   if (width == 0 || height == 0)
     return 0;
 
-  if (gc && (gc->gcv.function == GXset ||
-             gc->gcv.function == GXclear)) {
+  if (gc->gcv.function == GXset ||
+      gc->gcv.function == GXclear) {
     // "set" and "clear" are dumb drawing modes that ignore the source
     // bits and just draw solid rectangles.
     set_color (dst->cgc, (gc->gcv.function == GXset
@@ -559,29 +584,33 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
   CLIP (src, dst, y, height);
 # undef CLIP
 
-#if 0
-  Assert (src_rect.size.width  == dst_rect.size.width, "width out of sync");
-  Assert (src_rect.size.height == dst_rect.size.height, "height out of sync");
-  Assert (src_rect.origin.x >= 0 && src_rect.origin.y >= 0, "clip failed src_x");
-  Assert (dst_rect.origin.x >= 0 && dst_rect.origin.y >= 0, "clip failed dst_x");
-  Assert (src_rect.origin.y >= 0 && src_rect.origin.y >= 0, "clip failed src_y");
-  Assert (dst_rect.origin.y >= 0 && dst_rect.origin.y >= 0, "clip failed dst_y");
-  Assert (src_rect.origin.x  + src_rect.size.width <=
-          src_frame.origin.x + src_frame.size.width, "clip failed src_width");
-#endif
-  
   if (src_rect.size.width <= 0 || src_rect.size.height <= 0)
     return 0;
   
   NSObject *releaseme = 0;
   CGImageRef cgi;
   BOOL mask_p = NO;
+  BOOL free_cgi_p = NO;
 
   if (src->type == PIXMAP) {
 
-    // get a CGImage out of the pixmap CGContext -- it's the whole pixmap,
-    // but it presumably shares the data pointer instead of copying it.
-    cgi = CGBitmapContextCreateImage (src->cgc);
+    // If we are copying from a Pixmap to a Pixmap or Window, we must first
+    // copy the bits to an intermediary CGImage object, then copy that to the
+    // destination drawable's CGContext.
+    //
+    // (It doesn't seem to be possible to use NSCopyBits() to optimize the
+    // case of copying from a Pixmap back to itself, but I don't think that
+    // happens very often anyway.)
+    //
+    // First we get a CGImage out of the pixmap CGContext -- it's the whole
+    // pixmap, but it presumably shares the data pointer instead of copying
+    // it.  We then cache that CGImage it inside the Pixmap object.  Note:
+    // invalidate_drawable_cache() must be called to discard this any time a
+    // modification is made to the pixmap, or we'll end up re-using old bits.
+    //
+    if (!src->cgi)
+      src->cgi = CGBitmapContextCreateImage (src->cgc);
+    cgi = src->cgi;
 
     // if doing a sub-rect, trim it down.
     if (src_rect.origin.x    != src_frame.origin.x   ||
@@ -592,9 +621,8 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
       src_rect.origin.y = (src_frame.size.height -
                            src_rect.size.height - src_rect.origin.y);
       // This does not copy image data, so it should be fast.
-      CGImageRef cgi2 = CGImageCreateWithImageInRect (cgi, src_rect);
-      CGImageRelease (cgi);
-      cgi = cgi2;
+      cgi = CGImageCreateWithImageInRect (cgi, src_rect);
+      free_cgi_p = YES;
     }
 
     if (src->pixmap.depth == 1)
@@ -602,72 +630,60 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
 
   } else { /* (src->type == WINDOW) */
     
-    NSRect nsfrom;
+    NSRect nsfrom;    // NSRect != CGRect on 10.4
     nsfrom.origin.x    = src_rect.origin.x;
     nsfrom.origin.y    = src_rect.origin.y;
     nsfrom.size.width  = src_rect.size.width;
     nsfrom.size.height = src_rect.size.height;
 
-#if 1
-    // get the bits (desired sub-rectangle) out of the NSView via Cocoa.
-    //
-    NSBitmapImageRep *bm = [NSBitmapImageRep alloc];
-    [bm initWithFocusedViewRect:nsfrom];
-    unsigned char *data = [bm bitmapData];
-    int bps = [bm bitsPerSample];
-    int bpp = [bm bitsPerPixel];
-    int bpl = [bm bytesPerRow];
-    releaseme = bm;
-#endif
+    if (src == dst) {
 
-#if 0
-    // QuickDraw way (doesn't work, need NSQuickDrawView)
-    PixMapHandle pix = GetPortPixMap([src->window.view qdPort]);
-    char **data = GetPortPixMap (pix);
-    int bps = 8;
-    int bpp = 32;
-    int bpl = GetPixRowBytes (pix) & 0x3FFF;
-#endif
-
-#if 0
-    // get the bits (desired sub-rectangle) out of the raw frame buffer.
-    // (This renders wrong, and appears to be even slower anyway.)
-    //
-    int window_x, window_y;
-    XTranslateCoordinates (dpy, src, NULL, 0, 0, &window_x, &window_y, NULL);
-    window_x += nsfrom.origin.x;
-    window_y += (dst->frame.size.height
-                 - (nsfrom.origin.y + nsfrom.size.height));
-
-    unsigned char *data = (unsigned char *) 
-      CGDisplayAddressForPosition (dpy->cgdpy, window_x, window_y);
-    int bps = CGDisplayBitsPerSample (dpy->cgdpy);
-    int bpp = CGDisplayBitsPerPixel (dpy->cgdpy);
-    int bpl = CGDisplayBytesPerRow (dpy->cgdpy);
+      // If we are copying from a window to itself, we can use NSCopyBits()
+      // without first copying the rectangle to an intermediary CGImage.
+      // This is ~28% faster (but I *expected* it to be twice as fast...)
+      // (kumppa, bsod, decayscreen, memscroller, slidescreen, slip, xjack)
+      //
+      cgi = 0;
 
-#endif
+    } else {
 
-    // create a CGImage from those bits
-
-    CGDataProviderRef prov =
-      CGDataProviderCreateWithData (NULL, data, bpl * nsfrom.size.height,
-                                    NULL);
-    cgi = CGImageCreate (src_rect.size.width, src_rect.size.height,
-                         bps, bpp, bpl,
-                         dpy->colorspace, 
-                         /* Use whatever default bit ordering we got from
-                            initWithFocusedViewRect.  I would have assumed
-                            that it was (kCGImageAlphaNoneSkipFirst |
-                            kCGBitmapByteOrder32Host), but on Intel,
-                            it's not!
-                          */
-                         0,
-                         prov, 
-                         NULL,  /* decode[] */
-                         NO, /* interpolate */
-                         kCGRenderingIntentDefault);
-    //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
-    CGDataProviderRelease (prov);
+      // If we are copying from a Window to a Pixmap, we must first copy
+      // the bits to an intermediary CGImage object, then copy that to the
+      // Pixmap's CGContext.
+      //
+      NSBitmapImageRep *bm = [[NSBitmapImageRep alloc]
+                               initWithFocusedViewRect:nsfrom];
+      unsigned char *data = [bm bitmapData];
+      int bps = [bm bitsPerSample];
+      int bpp = [bm bitsPerPixel];
+      int bpl = [bm bytesPerRow];
+      releaseme = bm;
+
+      // create a CGImage from those bits.
+      // (As of 10.5, we could just do cgi = [bm CGImage] (it is autoreleased)
+      // but that method didn't exist in 10.4.)
+
+      CGDataProviderRef prov =
+        CGDataProviderCreateWithData (NULL, data, bpl * nsfrom.size.height,
+                                      NULL);
+      cgi = CGImageCreate (src_rect.size.width, src_rect.size.height,
+                           bps, bpp, bpl,
+                           dpy->colorspace, 
+                           /* Use whatever default bit ordering we got from
+                              initWithFocusedViewRect.  I would have assumed
+                              that it was (kCGImageAlphaNoneSkipFirst |
+                              kCGBitmapByteOrder32Host), but on Intel,
+                              it's not!
+                           */
+                           0,
+                           prov, 
+                           NULL,  /* decode[] */
+                           NO, /* interpolate */
+                           kCGRenderingIntentDefault);
+      free_cgi_p = YES;
+      //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace,"bad colorspace");
+      CGDataProviderRelease (prov);
+    }
   }
 
   if (mask_p) {                // src depth == 1
@@ -702,15 +718,29 @@ XCopyArea (Display *dpy, Drawable src, Drawable dst, GC gc,
       CGContextFillRect (dst->cgc, orig_dst_rect);
     }
 
-    // copy the CGImage onto the destination CGContext
-    //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
-    CGContextDrawImage (dst->cgc, dst_rect, cgi);
+    if (cgi) {
+      // copy the CGImage onto the destination CGContext
+      //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
+      CGContextDrawImage (dst->cgc, dst_rect, cgi);
+    } else {
+      // No cgi means src == dst, and both are Windows.
+      NSRect nsfrom;
+      nsfrom.origin.x    = src_rect.origin.x;    // NSRect != CGRect on 10.4
+      nsfrom.origin.y    = src_rect.origin.y;
+      nsfrom.size.width  = src_rect.size.width;
+      nsfrom.size.height = src_rect.size.height;
+      NSPoint nsto;
+      nsto.x             = dst_rect.origin.x;
+      nsto.y             = dst_rect.origin.y;
+      NSCopyBits (0, nsfrom, nsto);
+    }
 
     pop_gc (dst, gc);
   }
-  
-  CGImageRelease (cgi);
+
+  if (free_cgi_p) CGImageRelease (cgi);
   if (releaseme) [releaseme release];
+  invalidate_drawable_cache (dst);
   return 0;
 }
 
@@ -759,6 +789,7 @@ XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2)
   CGContextAddLineToPoint (d->cgc, p.x, p.y);
   CGContextStrokePath (d->cgc);
   pop_gc (d, gc);
+  invalidate_drawable_cache (d);
   return 0;
 }
 
@@ -797,6 +828,7 @@ XDrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
   if (closed_p) CGContextClosePath (d->cgc);
   CGContextStrokePath (d->cgc);
   pop_gc (d, gc);
+  invalidate_drawable_cache (d);
   return 0;
 }
 
@@ -821,6 +853,7 @@ XDrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
   }
   CGContextStrokePath (d->cgc);
   pop_gc (d, gc);
+  invalidate_drawable_cache (d);
   return 0;
 }
 
@@ -871,6 +904,7 @@ draw_rect (Display *dpy, Drawable d, GC gc,
 
   if (gc)
     pop_gc (d, gc);
+  invalidate_drawable_cache (d);
 }
 
 
@@ -906,6 +940,7 @@ XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n)
     rects++;
   }
   pop_gc (d, gc);
+  invalidate_drawable_cache (d);
   return 0;
 }
 
@@ -949,6 +984,7 @@ XFillPolygon (Display *dpy, Drawable d, GC gc,
   else
     CGContextFillPath (d->cgc);
   pop_gc (d, gc);
+  invalidate_drawable_cache (d);
   return 0;
 }
 
@@ -1000,6 +1036,7 @@ draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
   }
 
   pop_gc (d, gc);
+  invalidate_drawable_cache (d);
   return 0;
 }
 
@@ -1172,7 +1209,9 @@ Status
 XAllocColor (Display *dpy, Colormap cmap, XColor *color)
 {
   // store 32 bit ARGB in the pixel field.
-  color->pixel = ((                       0xFF  << 24) |
+  // (The uint32_t is so that 0xFF000000 doesn't become 0xFFFFFFFFFF000000)
+  color->pixel = (uint32_t)
+                 ((                       0xFF  << 24) |
                   (((color->red   >> 8) & 0xFF) << 16) |
                   (((color->green >> 8) & 0xFF) <<  8) |
                   (((color->blue  >> 8) & 0xFF)      ));
@@ -1306,17 +1345,18 @@ ximage_putpixel_1 (XImage *ximage, int x, int y, unsigned long pixel)
 static unsigned long
 ximage_getpixel_32 (XImage *ximage, int x, int y)
 {
-  return *((unsigned long *) ximage->data +
-           (y * (ximage->bytes_per_line >> 2)) +
-           x);
+  return ((unsigned long)
+          *((uint32_t *) ximage->data +
+            (y * (ximage->bytes_per_line >> 2)) +
+            x));
 }
 
 static int
 ximage_putpixel_32 (XImage *ximage, int x, int y, unsigned long pixel)
 {
-  *((unsigned long *) ximage->data +
+  *((uint32_t *) ximage->data +
     (y * (ximage->bytes_per_line >> 2)) +
-    x) = pixel;
+    x) = (uint32_t) pixel;
   return 0;
 }
 
@@ -1479,6 +1519,14 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
 {
   CGRect wr = d->frame;
 
+  Assert (gc, "no GC");
+  Assert ((w < 65535), "improbably large width");
+  Assert ((h < 65535), "improbably large height");
+  Assert ((src_x  < 65535 && src_x  > -65535), "improbably large src_x");
+  Assert ((src_y  < 65535 && src_y  > -65535), "improbably large src_y");
+  Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
+  Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
+
   // Clip width and height to the bounds of the Drawable
   //
   if (dest_x + w > wr.size.width) {
@@ -1509,8 +1557,8 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
   if (w <= 0 || h <= 0)
     return 0;
 
-  if (gc && (gc->gcv.function == GXset ||
-             gc->gcv.function == GXclear)) {
+  if (gc->gcv.function == GXset ||
+      gc->gcv.function == GXclear) {
     // "set" and "clear" are dumb drawing modes that ignore the source
     // bits and just draw solid rectangles.
     set_color (d->cgc, (gc->gcv.function == GXset
@@ -1595,6 +1643,8 @@ XPutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
     CGImageRelease (mask);
   }
 
+  invalidate_drawable_cache (d);
+
   return 0;
 }
 
@@ -1605,33 +1655,40 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
            unsigned long plane_mask, int format)
 {
   const unsigned char *data = 0;
-  int depth, ibpp, ibpl;
+  int depth, ibpp, ibpl, alpha_first_p;
   NSBitmapImageRep *bm = 0;
   
+  Assert ((width  < 65535), "improbably large width");
+  Assert ((height < 65535), "improbably large height");
+  Assert ((x < 65535 && x > -65535), "improbably large x");
+  Assert ((y < 65535 && y > -65535), "improbably large y");
+
   if (d->type == PIXMAP) {
     depth = d->pixmap.depth;
+    alpha_first_p = 1; // we created it with kCGImageAlphaNoneSkipFirst.
     ibpp = CGBitmapContextGetBitsPerPixel (d->cgc);
     ibpl = CGBitmapContextGetBytesPerRow (d->cgc);
     data = CGBitmapContextGetData (d->cgc);
     Assert (data, "CGBitmapContextGetData failed");
   } else {
     // get the bits (desired sub-rectangle) out of the NSView
-    bm = [NSBitmapImageRep alloc];
     NSRect nsfrom;
     nsfrom.origin.x = x;
     nsfrom.origin.y = y;
     nsfrom.size.width = width;
     nsfrom.size.height = height;
-    [bm initWithFocusedViewRect:nsfrom];
+    bm = [[NSBitmapImageRep alloc] initWithFocusedViewRect:nsfrom];
     depth = 32;
+    alpha_first_p = ([bm bitmapFormat] & NSAlphaFirstBitmapFormat);
     ibpp = [bm bitsPerPixel];
     ibpl = [bm bytesPerRow];
     data = [bm bitmapData];
     Assert (data, "NSBitmapImageRep initWithFocusedViewRect failed");
-
-    data += (y * ibpl) + (x * (ibpp/8));
   }
   
+  // data points at (x,y) with ibpl rowstride.  ignore x,y from now on.
+  data += (y * ibpl) + (x * (ibpp/8));
+  
   format = (depth == 1 ? XYPixmap : ZPixmap);
   XImage *image = XCreateImage (dpy, 0, depth, format, 0, 0, width, height,
                                 0, 0);
@@ -1642,19 +1699,23 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
   /* both PPC and Intel use word-ordered ARGB frame buffers, which
      means that on Intel it is BGRA when viewed by bytes (And BGR
      when using 24bpp packing).
+
+     BUT! Intel-64 stores alpha at the other end! 32bit=RGBA, 64bit=ARGB.
+     The NSAlphaFirstBitmapFormat bit in bitmapFormat seems to be the
+     indicator of this latest kink.
    */
   int xx, yy;
   if (depth == 1) {
     const unsigned char *iline = data;
-    for (yy = y; yy < y+height; yy++) {
+    for (yy = 0; yy < height; yy++) {
 
       const unsigned char *iline2 = iline;
-      for (xx = x; xx < x+width; xx++) {
+      for (xx = 0; xx < width; xx++) {
 
-        iline2++;                     // ignore b or a
-        iline2++;                     // ignore g or r
-        unsigned char r = *iline2++;  //        r or g
-        if (ibpp == 32) iline2++;     // ignore a or b
+        iline2++;                     // ignore R  or  A  or  A  or  B
+        iline2++;                     // ignore G  or  B  or  R  or  G
+        unsigned char r = *iline2++;  // use    B  or  G  or  G  or  R
+        if (ibpp == 32) iline2++;     // ignore A  or  R  or  B  or  A
 
         XPutPixel (image, xx, yy, (r ? 1 : 0));
       }
@@ -1664,24 +1725,38 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
     Assert (ibpp == 24 || ibpp == 32, "weird obpp");
     const unsigned char *iline = data;
     unsigned char *oline = (unsigned char *) image->data;
-    oline += (y * obpl);
-    for (yy = y; yy < y+height; yy++) {
+    for (yy = 0; yy < height; yy++) {
 
       const unsigned char *iline2 = iline;
       unsigned char *oline2 = oline;
-      for (xx = x; xx < x+width; xx++) {
-
-        unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF);
-        unsigned char r = *iline2++;
-        unsigned char g = *iline2++;
-        unsigned char b = *iline2++;
-        unsigned long pixel = ((a << 24) |
-                               (r << 16) |
-                               (g <<  8) |
-                               (b <<  0));
-        *((unsigned int *) oline2) = pixel;
-        oline2 += 4;
-     }
+
+      if (alpha_first_p)                       // ARGB
+        for (xx = 0; xx < width; xx++) {
+          unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF);
+          unsigned char r = *iline2++;
+          unsigned char g = *iline2++;
+          unsigned char b = *iline2++;
+          uint32_t pixel = ((a << 24) |
+                            (r << 16) |
+                            (g <<  8) |
+                            (b <<  0));
+          *((uint32_t *) oline2) = pixel;
+          oline2 += 4;
+        }
+      else                                     // RGBA
+        for (xx = 0; xx < width; xx++) {
+          unsigned char r = *iline2++;
+          unsigned char g = *iline2++;
+          unsigned char b = *iline2++;
+          unsigned char a = (ibpp == 32 ? (*iline2++) : 0xFF);
+          uint32_t pixel = ((a << 24) |
+                            (r << 16) |
+                            (g <<  8) |
+                            (b <<  0));
+          *((uint32_t *) oline2) = pixel;
+          oline2 += 4;
+        }
+
       oline += obpl;
       iline += ibpl;
     }
@@ -1692,34 +1767,113 @@ XGetImage (Display *dpy, Drawable d, int x, int y,
   return image;
 }
 
+
+/* Returns a transformation matrix to do rotation as per the provided
+   EXIF "Orientation" value.
+ */
+static CGAffineTransform
+exif_rotate (int rot, CGSize rect)
+{
+  CGAffineTransform trans = CGAffineTransformIdentity;
+  switch (rot) {
+  case 2:              // flip horizontal
+    trans = CGAffineTransformMakeTranslation (rect.width, 0);
+    trans = CGAffineTransformScale (trans, -1, 1);
+    break;
+
+  case 3:              // rotate 180
+    trans = CGAffineTransformMakeTranslation (rect.width, rect.height);
+    trans = CGAffineTransformRotate (trans, M_PI);
+    break;
+
+  case 4:              // flip vertical
+    trans = CGAffineTransformMakeTranslation (0, rect.height);
+    trans = CGAffineTransformScale (trans, 1, -1);
+    break;
+
+  case 5:              // transpose (UL-to-LR axis)
+    trans = CGAffineTransformMakeTranslation (rect.height, rect.width);
+    trans = CGAffineTransformScale (trans, -1, 1);
+    trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
+    break;
+
+  case 6:              // rotate 90
+    trans = CGAffineTransformMakeTranslation (0, rect.width);
+    trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
+    break;
+
+  case 7:              // transverse (UR-to-LL axis)
+    trans = CGAffineTransformMakeScale (-1, 1);
+    trans = CGAffineTransformRotate (trans, M_PI / 2);
+    break;
+
+  case 8:              // rotate 270
+    trans = CGAffineTransformMakeTranslation (rect.height, 0);
+    trans = CGAffineTransformRotate (trans, M_PI / 2);
+    break;
+
+  default: 
+    break;
+  }
+
+  return trans;
+}
+
+
 void
-jwxyz_draw_NSImage (Display *dpy, Drawable d, void *nsimg_arg,
-                    XRectangle *geom_ret)
+jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d, 
+                                Bool nsimg_p, void *img_arg,
+                               XRectangle *geom_ret, int exif_rotation)
 {
-  NSImage *nsimg = (NSImage *) nsimg_arg;
+  CGImageRef cgi;
+  CGImageSourceRef cgsrc;
+  NSSize imgr;
+
+  if (nsimg_p) {
+
+    NSImage *nsimg = (NSImage *) img_arg;
+    imgr = [nsimg size];
+
+    // convert the NSImage to a CGImage via the toll-free-bridging 
+    // of NSData and CFData...
+    //
+    NSData *nsdata = [NSBitmapImageRep
+                       TIFFRepresentationOfImageRepsInArray:
+                         [nsimg representations]];
+    CFDataRef cfdata = (CFDataRef) nsdata;
+    cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
+    cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
+
+  } else {
+    cgi = (CGImageRef) img_arg;
+    imgr.width  = CGImageGetWidth (cgi);
+    imgr.height = CGImageGetHeight (cgi);
+  }
+
+  Bool rot_p = (exif_rotation >= 5);
+
+  if (rot_p)
+    imgr = NSMakeSize (imgr.height, imgr.width);
 
-  // convert the NSImage to a CGImage via the toll-free-bridging 
-  // of NSData and CFData...
-  //
-  NSData *nsdata = [NSBitmapImageRep
-                        TIFFRepresentationOfImageRepsInArray:
-                          [nsimg representations]];
-  CFDataRef cfdata = (CFDataRef) nsdata;
-  CGImageSourceRef cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
-  CGImageRef cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
-
-  NSSize imgr = [nsimg size];
   CGRect winr = d->frame;
   float rw = winr.size.width  / imgr.width;
   float rh = winr.size.height / imgr.height;
   float r = (rw < rh ? rw : rh);
 
-  CGRect dst;
+  CGRect dst, dst2;
   dst.size.width  = imgr.width  * r;
   dst.size.height = imgr.height * r;
   dst.origin.x = (winr.size.width  - dst.size.width)  / 2;
   dst.origin.y = (winr.size.height - dst.size.height) / 2;
 
+  dst2.origin.x = dst2.origin.y = 0;
+  if (rot_p) {
+    dst2.size.width = dst.size.height; 
+    dst2.size.height = dst.size.width;
+  } else {
+    dst2.size = dst.size;
+  }
+
   // Clear the part not covered by the image to background or black.
   //
   if (d->type == WINDOW)
@@ -1729,11 +1883,22 @@ jwxyz_draw_NSImage (Display *dpy, Drawable d, void *nsimg_arg,
     draw_rect (dpy, d, 0, 0, 0, winr.size.width, winr.size.height, NO, YES);
   }
 
+  CGAffineTransform trans = 
+    exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size);
+
+  CGContextSaveGState (d->cgc);
+  CGContextConcatCTM (d->cgc, 
+                      CGAffineTransformMakeTranslation (dst.origin.x,
+                                                        dst.origin.y));
+  CGContextConcatCTM (d->cgc, trans);
   //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
-  CGContextDrawImage (d->cgc, dst, cgi);
+  CGContextDrawImage (d->cgc, dst2, cgi);
+  CGContextRestoreGState (d->cgc);
 
-  CFRelease (cgsrc);
-  CGImageRelease (cgi);
+  if (nsimg_p) {
+    CFRelease (cgsrc);
+    CGImageRelease (cgi);
+  }
 
   if (geom_ret) {
     geom_ret->x = dst.origin.x;
@@ -1741,6 +1906,8 @@ jwxyz_draw_NSImage (Display *dpy, Drawable d, void *nsimg_arg,
     geom_ret->width  = dst.size.width;
     geom_ret->height = dst.size.height;
   }
+
+  invalidate_drawable_cache (d);
 }
 
 
@@ -1777,9 +1944,10 @@ XCreatePixmap (Display *dpy, Drawable d,
   p->frame.size.width  = width;
   p->frame.size.height = height;
   p->pixmap.depth      = depth;
+  p->pixmap.cgc_buffer = data;
   
   /* Quartz doesn't have a 1bpp image type.
-     We used to use 8bpp gray images instead of 1bpp, but some Mac video
+     Used to use 8bpp gray images instead of 1bpp, but some Mac video cards
      don't support that!  So we always use 32bpp, regardless of depth. */
 
   p->cgc = CGBitmapContextCreate (data, width, height,
@@ -1798,20 +1966,41 @@ int
 XFreePixmap (Display *d, Pixmap p)
 {
   Assert (p->type == PIXMAP, "not a pixmap");
+  invalidate_drawable_cache (p);
   CGContextRelease (p->cgc);
+  if (p->pixmap.cgc_buffer)
+    free (p->pixmap.cgc_buffer);
   free (p);
   return 0;
 }
 
 
 static Pixmap
-copy_pixmap (Pixmap p)
+copy_pixmap (Display *dpy, Pixmap p)
 {
   if (!p) return 0;
   Assert (p->type == PIXMAP, "not a pixmap");
+
+  int width  = p->frame.size.width;
+  int height = p->frame.size.height;
+  char *data = (char *) malloc (width * height * 4);
+  if (! data) return 0;
+
+  memcpy (data, p->pixmap.cgc_buffer, width * height * 4);
+
   Pixmap p2 = (Pixmap) malloc (sizeof (*p2));
   *p2 = *p;
-  CGContextRetain (p2->cgc);   // #### is this ok? need to copy it instead?
+  p2->cgi = 0;
+  p2->pixmap.cgc_buffer = data;
+  p2->cgc = CGBitmapContextCreate (data, width, height,
+                                   8, /* bits per component */
+                                   width * 4, /* bpl */
+                                   dpy->colorspace,
+                                   // Without this, it returns 0...
+                                   kCGImageAlphaNoneSkipFirst
+                                   );
+  Assert (p2->cgc, "could not create CGBitmapContext");
+
   return p2;
 }
 
@@ -1842,6 +2031,15 @@ copy_pixmap (Pixmap p)
 static void
 query_font (Font fid)
 {
+  if (!fid || !fid->nsfont) {
+    NSLog(@"no NSFont in fid");
+    abort();
+  }
+  if (![fid->nsfont fontName]) {
+    NSLog(@"broken NSFont in fid");
+    abort();
+  }
+
   int first = 32;
   int last = 255;
 
@@ -1890,6 +2088,14 @@ query_font (Font fid)
       NSTextStorage *ts = [[NSTextStorage alloc] initWithString:nsstr];
       [ts setFont:fid->nsfont];
       NSLayoutManager *lm = [[NSLayoutManager alloc] init];
+
+      /* Without this, the layout manager ends up on a queue somewhere and is
+         referenced again after we return to the command loop.  Since we don't
+         use this layout manager again, by that time it may have been garbage
+         collected, and we crash.  Setting this seems to cause `lm' to no 
+         longer be referenced once we exit this block. */
+      [lm setBackgroundLayoutEnabled:NO];
+
       NSTextContainer *tc = [[NSTextContainer alloc] init];
       [lm addTextContainer:tc];
       [tc release];    // lm retains tc
@@ -1993,7 +2199,7 @@ copy_font (Font fid)
 
   // copy the other pointers
   fid2->ps_name = strdup (fid->ps_name);
-  [fid2->nsfont retain];
+//  [fid2->nsfont retain];
   fid2->metrics.fid = fid2;
 
   return fid2;
@@ -2004,23 +2210,45 @@ static NSFont *
 try_font (BOOL fixed, BOOL bold, BOOL ital, BOOL serif, float size,
           char **name_ret)
 {
-  const char *prefix = (fixed ? "Monaco" : (serif ? "Times" : "Helvetica"));
-  const char *suffix = (bold && ital
-                        ? (serif ? "-BoldItalic" : "-BoldOblique")
-                        : (bold ? "-Bold" :
-                           ital ? (serif ? "-Italic" : "-Oblique") : ""));
-  char *name = (char *) malloc (strlen(prefix) + strlen(suffix) + 1);
-  strcpy (name, prefix);
-  strcat (name, suffix);
+  Assert (size > 0, "zero font size");
+  const char *name;
+
+  if (fixed) {
+    // 
+    // "Monaco" only exists in plain.
+    // "LucidaSansTypewriterStd" gets an AGL bad value error.
+    // 
+    if (bold && ital) name = "Courier-BoldOblique";
+    else if (bold)    name = "Courier-Bold";
+    else if (ital)    name = "Courier-Oblique";
+    else              name = "Courier";
+
+  } else if (serif) {
+    // 
+    // "Georgia" looks better than "Times".
+    // 
+    if (bold && ital) name = "Georgia-BoldItalic";
+    else if (bold)    name = "Georgia-Bold";
+    else if (ital)    name = "Georgia-Italic";
+    else              name = "Georgia";
+
+  } else {
+    // 
+    // "Geneva" only exists in plain.
+    // "LucidaSansStd-BoldItalic" gets an AGL bad value error.
+    // "Verdana" renders smoother than "Helvetica" for some reason.
+    // 
+    if (bold && ital) name = "Verdana-BoldItalic";
+    else if (bold)    name = "Verdana-Bold";
+    else if (ital)    name = "Verdana-Italic";
+    else              name = "Verdana";
+  }
 
   NSString *nsname = [NSString stringWithCString:name
                                         encoding:NSUTF8StringEncoding];
   NSFont *f = [NSFont fontWithName:nsname size:size];
-  if (f) {
-    *name_ret = name;
-  } else {
-    free (name);
-  }
+  if (f)
+    *name_ret = strdup(name);
   return f;
 }
 
@@ -2055,8 +2283,8 @@ try_native_font (const char *name, char **name_ret, float *size_ret)
 static NSFont *
 random_font (BOOL bold, BOOL ital, float size, char **name_ret)
 {
-  NSFontTraitMask mask = ((bold ? NSUnboldFontMask   : NSBoldFontMask) |
-                          (ital ? NSUnitalicFontMask : NSItalicFontMask));
+  NSFontTraitMask mask = ((bold ? NSBoldFontMask   : NSUnboldFontMask) |
+                          (ital ? NSItalicFontMask : NSUnitalicFontMask));
   NSArray *fonts = [[NSFontManager sharedFontManager]
                      availableFontNamesWithTraits:mask];
   if (!fonts) return 0;
@@ -2196,6 +2424,7 @@ XLoadFont (Display *dpy, const char *name)
     NSLog(@"no NSFont for \"%s\"", name);
     abort();
   }
+  CFRetain (fid->nsfont);   // needed for garbage collection?
 
   //NSLog(@"parsed \"%s\" to %s %.1f", name, fid->ps_name, fid->size);
 
@@ -2205,40 +2434,6 @@ XLoadFont (Display *dpy, const char *name)
 }
 
 
-/* This translates the NSFont into the numbers that aglUseFont() wants.
- */
-int
-jwxyz_font_info (Font f, int *size_ret, int *face_ret)
-{
-  char *name = strdup (f->ps_name);
-  char *dash = strchr (name, '-');
-  int flags = 0;
-  int size = f->size;
-  if (dash) {
-    // 0 = plain; 1=B; 2=I; 3=BI; 4=U; 5=UB; etc.
-    if (strcasestr (dash, "bold"))    flags |= 1;
-    if (strcasestr (dash, "italic"))  flags |= 2;
-    if (strcasestr (dash, "oblique")) flags |= 2;
-    *dash = 0;
-  }
-  NSString *nname = [NSString stringWithCString:name
-                                       encoding:NSUTF8StringEncoding];
-  ATSFontFamilyRef id =
-    ATSFontFamilyFindFromName ((CFStringRef) nname, kATSOptionFlagsDefault);
-
-
-  // WTF?  aglUseFont gets a BadValue if size is small!!
-  if (size < 9) size = 9;
-
-  //NSLog (@"font %s %.1f => %d %d %d", f->ps_name, f->size, id, flags, size);
-  Assert (id >= 0, "no ATS font family");
-
-  *size_ret = size;
-  *face_ret = flags;
-  return id;
-}
-
-
 XFontStruct *
 XLoadQueryFont (Display *dpy, const char *name)
 {
@@ -2251,7 +2446,15 @@ XUnloadFont (Display *dpy, Font fid)
 {
   free (fid->ps_name);
   free (fid->metrics.per_char);
-  [fid->nsfont release];
+
+  // #### DAMMIT!  I can't tell what's going wrong here, but I keep getting
+  //      crashes in [NSFont ascender] <- query_font, and it seems to go away
+  //      if I never release the nsfont.  So, fuck it, we'll just leak fonts.
+  //      They're probably not very big...
+  //
+  //  [fid->nsfont release];
+  //  CFRelease (fid->nsfont);
+
   free (fid);
   return 0;
 }
@@ -2290,15 +2493,11 @@ XSetFont (Display *dpy, GC gc, Font fid)
   if (gc->gcv.font)
     XUnloadFont (dpy, gc->gcv.font);
   gc->gcv.font = copy_font (fid);
+  [gc->gcv.font->nsfont retain];
+  CFRetain (gc->gcv.font->nsfont);   // needed for garbage collection?
   return 0;
 }
 
-Font  // really GContext
-XGContextFromGC (GC gc)    // WTF is this actually supposed to do?
-{
-  return gc->gcv.font;
-}
-
 int
 XTextExtents (XFontStruct *f, const char *s, int length,
               int *dir_ret, int *ascent_ret, int *descent_ret,
@@ -2341,8 +2540,12 @@ static void
 set_font (CGContextRef cgc, GC gc)
 {
   Font font = gc->gcv.font;
-  if (! font)
-    font = gc->gcv.font = XLoadFont (0, 0);
+  if (! font) {
+    font = XLoadFont (0, 0);
+    gc->gcv.font = font;
+    [gc->gcv.font->nsfont retain];
+    CFRetain (gc->gcv.font->nsfont);   // needed for garbage collection?
+  }
   CGContextSelectFont (cgc, font->ps_name, font->size, kCGEncodingMacRoman);
   CGContextSetTextMatrix (cgc, CGAffineTransformIdentity);
 }
@@ -2378,15 +2581,15 @@ draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
   set_font (d->cgc, gc);
 
   CGContextSetTextDrawingMode (d->cgc, kCGTextFill);
-  if (gc->gcv.antialias_p)
-    CGContextSetShouldAntialias (d->cgc, YES);  // always antialias text
+  if (gc->gcv.antialias_p)
+    CGContextSetShouldAntialias (d->cgc, YES);
   CGContextShowTextAtPoint (d->cgc,
                             wr.origin.x + x,
                             wr.origin.y + wr.size.height - y,
                             str, len);
   pop_gc (d, gc);
 
-#else /* !0 */
+# else /* !0 */
 
   /* The Cocoa way...
    */
@@ -2414,8 +2617,9 @@ draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
   pos.y = wr.origin.y + wr.size.height - y - gc->gcv.font->metrics.descent;
   [nsstr drawAtPoint:pos withAttributes:attr];
 
-#endif  /* 0 */
+# endif  /* 0 */
 
+  invalidate_drawable_cache (d);
   return 0;
 }
 
@@ -2509,7 +2713,7 @@ XSetClipMask (Display *dpy, GC gc, Pixmap m)
     CGImageRelease (gc->clip_mask);
   }
 
-  gc->gcv.clip_mask = copy_pixmap (m);
+  gc->gcv.clip_mask = copy_pixmap (dpy, m);
   if (gc->gcv.clip_mask)
     gc->clip_mask = CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc);
   else