Subject: Fix for image/alpha rendering under hidpi/Retina displays on Mac OS; thanks to Marc Culler for assistance

From: Kevin Walzer <[email-masked]>
Date: Fri, 15 Jul 2016 10:47:18 +0000

---
 generic/tkImgPhInstance.c | 25 +++++++++++++++++++++++--
 macosx/tkMacOSXDraw.c     |  6 ++++--
 macosx/tkMacOSXXStubs.c   | 37 ++++++++++++++++++++++++++++++-------
 xlib/X11/Xlib.h           |  3 +++
 4 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/generic/tkImgPhInstance.c b/generic/tkImgPhInstance.c
index 666a9b0..bd152f2 100644
--- a/generic/tkImgPhInstance.c
+++ b/generic/tkImgPhInstance.c
@@ -404,6 +404,9 @@ TkImgPhotoGet(
  *
  *	Note that Win32 pre-defines those operations that we really need.
  *
+ *	Note that on MacOS, if the background comes from a Retina display
+ *	then it will be twice as wide and twice as high as the photoimage.
+ *
  *----------------------------------------------------------------------
  */
 
@@ -433,7 +436,16 @@ BlendComplexAlpha(
     unsigned long pixel;
     unsigned char r, g, b, alpha, unalpha, *masterPtr;
     unsigned char *alphaAr = iPtr->masterPtr->pix32;
-
+#if defined(MAC_OSX_TK)
+    /* Background "pixels" are actually 2^pp x 2^pp blocks of subpixels.  Each
+     * block gets blended with the color of one image pixel.  Since we iterate
+     * over the background subpixels, we reset the width and height to the
+     * subpixel dimensions of the background image we are using.
+     */
+    int pp = bgImg->pixelpower;
+    width = width << pp;
+    height = height << pp;
+#endif
     /*
      * This blending is an integer version of the Source-Over compositing rule
      * (see Porter&Duff, "Compositing Digital Images", proceedings of SIGGRAPH
@@ -532,9 +544,16 @@ BlendComplexAlpha(
 #endif /* !_WIN32 && !MAC_OSX_TK */
 
     for (y = 0; y < height; y++) {
+# if !defined(MAC_OSX_TK)
 	line = (y + yOffset) * iPtr->masterPtr->width;
 	for (x = 0; x < width; x++) {
 	    masterPtr = alphaAr + ((line + x + xOffset) * 4);
+#else
+	/* Repeat each image row and column 2^pp times. */
+	line = ((y>>pp) + yOffset) * iPtr->masterPtr->width;
+	for (x = 0; x < width; x++) {
+	    masterPtr = alphaAr + ((line + (x>>pp) + xOffset) * 4);
+#endif
 	    alpha = masterPtr[3];
 
 	    /*
@@ -635,7 +654,9 @@ TkImgPhotoDisplay(
 		(unsigned int)width, (unsigned int)height, AllPlanes, ZPixmap);
 	if (bgImg == NULL) {
 	    Tk_DeleteErrorHandler(handler);
-	    /* We failed to get the image so draw without blending alpha. It's the best we can do */
+	    /* We failed to get the image, so draw without blending alpha.
+	     * It's the best we can do.
+	     */
 	    goto fallBack;
 	}
 
diff --git a/macosx/tkMacOSXDraw.c b/macosx/tkMacOSXDraw.c
index d9b909b..f48538d 100644
--- a/macosx/tkMacOSXDraw.c
+++ b/macosx/tkMacOSXDraw.c
@@ -399,9 +399,11 @@ TkPutImage(
 	CGImageRef img = CreateCGImageWithXImage(image);
 
 	if (img) {
+	    /* If the XImage has big pixels, rescale the source dimensions.*/
+	    int pp = image->pixelpower;
 	    DrawCGImage(d, gc, dc.context, img, gc->foreground, gc->background,
-		    CGRectMake(0, 0, image->width, image->height),
-		    CGRectMake(src_x, src_y, width, height),
+		    CGRectMake(0, 0, image->width<<pp, image->height<<pp),
+		    CGRectMake(src_x<<pp, src_y<<pp, width<<pp, height<<pp),
 		    CGRectMake(dest_x, dest_y, width, height));
 	    CFRelease(img);
 	} else {
diff --git a/macosx/tkMacOSXXStubs.c b/macosx/tkMacOSXXStubs.c
index 5d6ffb9..a2d175b 100644
--- a/macosx/tkMacOSXXStubs.c
+++ b/macosx/tkMacOSXXStubs.c
@@ -805,6 +805,10 @@ XCreateImage(
     ximage->format = format;
     ximage->data = data;
     ximage->obdata = NULL;
+    /* The default pixelpower is 0.  This must be explicitly set to 1 in the
+     * case of an XImage extracted from a Retina display.
+     */
+    ximage->pixelpower = 0;
 
     if (format == ZPixmap) {
 	ximage->bits_per_pixel = 32;
@@ -856,7 +860,8 @@ XCreateImage(
  * Results:
  *	Returns a newly allocated XImage containing the data from the given
  *	rectangle of the given drawable, or NULL if the XImage could not be
- *     constructed.
+ *	constructed.  NOTE: If we are copying from a window on a Retina
+ *	display, the dimensions of the XImage will be 2*width x 2*height.
  *
  * Side effects:
  *	None.
@@ -885,7 +890,19 @@ XGetImage(
     int	        bitmap_pad = 0;
     int	        bytes_per_row = 4*width;
     int                size;
-    TkMacOSXDbgMsg("XGetImage");
+    MacDrawable *macDraw = (MacDrawable *) d;
+    NSWindow *win = TkMacOSXDrawableWindow(d);
+    /* This code assumes that backing scale factors are integers.  Currently
+     * Retina displays use a scale factor of 2.0 and normal displays use 1.0.
+     * We do not support any other values here.
+     */
+    int scalefactor = 1;
+    if (win && [win respondsToSelector:@selector(backingScaleFactor)]) { 
+	scalefactor = ([win backingScaleFactor] == 2.0) ? 2 : 1;
+    }
+    int scaled_height = height * scalefactor;
+    int scaled_width = width * scalefactor;
+
     if (format == ZPixmap) {
 	if (width == 0 || height == 0) {
 	    /* This happens all the time.
@@ -894,7 +911,7 @@ XGetImage(
 	    return NULL;
 	}
 
-	bitmap_rep =  BitmapRepFromDrawableRect(d, x, y,width, height);
+	bitmap_rep =  BitmapRepFromDrawableRect(d, x, y, width, height);
 	bitmap_fmt = [bitmap_rep bitmapFormat];
 
 	if ( bitmap_rep == Nil                        ||
@@ -913,7 +930,9 @@ XGetImage(
 	if ( [bitmap_rep isPlanar ] == 0 &&
 	     [bitmap_rep samplesPerPixel] == 4 ) {
 	    bytes_per_row = [bitmap_rep bytesPerRow];
-	    size = bytes_per_row*height;
+	    assert(bytes_per_row == 4 * scaled_width);
+	    assert([bitmap_rep bytesPerPlane] == bytes_per_row * scaled_height);
+	    size = bytes_per_row*scaled_height;
 	    image_data = (char*)[bitmap_rep bitmapData];
 	    if ( image_data ) {
 		int row, n, m;
@@ -924,7 +943,7 @@ XGetImage(
 		*/
 		if (bitmap_fmt == 0) {
 		    /* BGRA */
-		    for (row=0, n=0; row<height; row++, n+=bytes_per_row) {
+		    for (row=0, n=0; row<scaled_height; row++, n+=bytes_per_row) {
 			for (m=n; m<n+bytes_per_row; m+=4) {
 			    *(bitmap+m)   = *(image_data+m+2);
 			    *(bitmap+m+1) = *(image_data+m+1);
@@ -934,7 +953,7 @@ XGetImage(
 		    }
 		} else {
 		    /* ABGR */
-		    for (row=0, n=0; row<height; row++, n+=bytes_per_row) {
+		    for (row=0, n=0; row<scaled_height; row++, n+=bytes_per_row) {
 			for (m=n; m<n+bytes_per_row; m+=4) {
 			    *(bitmap+m)   = *(image_data+m+3);
 			    *(bitmap+m+1) = *(image_data+m+2);
@@ -947,7 +966,11 @@ XGetImage(
 	}
 	if (bitmap) {
 	    imagePtr = XCreateImage(display, NULL, depth, format, offset,
-				    (char*)bitmap, width, height, bitmap_pad, bytes_per_row);
+				    (char*)bitmap, scaled_width, scaled_height,
+				    bitmap_pad, bytes_per_row);
+	    if (scalefactor == 2) {
+	    	imagePtr->pixelpower = 1;
+	     }
 	    [ns_image removeRepresentation:bitmap_rep]; /*releases the rep*/
 	    [ns_image release];
 	}
diff --git a/xlib/X11/Xlib.h b/xlib/X11/Xlib.h
index 667bdc7..8d8ec68 100644
--- a/xlib/X11/Xlib.h
+++ b/xlib/X11/Xlib.h
@@ -330,6 +330,9 @@ typedef struct _XImage {
     unsigned long green_mask;
     unsigned long blue_mask;
     XPointer obdata;		/* hook for the object routines to hang on */
+#if defined(MAC_OSX_TK)
+    int pixelpower;		/* n such that pixels are 2^n x 2^n blocks*/
+#endif
     struct funcs {		/* image manipulation routines */
 	struct _XImage *(*create_image)();
 #if NeedFunctionPrototypes
-- 
cgit v0.12