From cb4a783dafd197e9caf1ce9361d605f0a33a6ba7 Mon Sep 17 00:00:00 2001
From: Christopher Chavez <chrischavez@gmx.us>
Date: Fri, 14 Aug 2020 08:48:06 -0500
Subject: [PATCH 6/6] Aqua X11 emulation: support Retina window source

Existing programs/libraries expect XGetImage() to return either an
XImage with the requested width and height, or NULL. This means that
a capture of a real window on a Retina display must be downscaled
before being returned by XGetImage() for compatibility.

To support this, a parameter force_1x_scale is added to
TkMacOSXCreateCGImageFromDrawableRect(), which if true will downscale
the returned CGImage to the requested width and height. Otherwise,
capturing a window on a Retina display will return a CGImage having
twice the requested width and height.

If programs should be able to retrieve unscaled captures of windows on
Retina displays, then a separate API is needed. See
https://core.tcl-lang.org/tk/info/fcd6717d8b

Not fully tested. Would be particularly interested in a way to test
XCopyArea() between non-pixmaps. XCopyPlane() might be unfinished.
---
 macosx/tkMacOSXDraw.c    | 34 +++++++++++++++++++++++++++++++++-
 macosx/tkMacOSXImage.c   |  2 +-
 macosx/tkMacOSXPrivate.h |  1 +
 3 files changed, 35 insertions(+), 2 deletions(-)

diff --git macosx/tkMacOSXDraw.c macosx/tkMacOSXDraw.c
index 339a527cf..2d3ef9f17 100644
--- macosx/tkMacOSXDraw.c
+++ macosx/tkMacOSXDraw.c
@@ -123,6 +123,11 @@ TkMacOSXInitCGDrawing(
  *      with origin at the top left, as used by XImage and CGImage, not bottom
  *      left as used by NSView.
  *
+ *	If force_1x_scale is true, then the returned CGImage will be downscaled
+ *	if necessary to have the requested width and height. Othewise, for
+ *	windows on Retina displays, the width and height of the returned CGImage
+ *	will be twice the requested width and height.
+ *
  * Side effects:
  *	If successful, allocates a new CGImage.
  *
@@ -132,6 +137,7 @@ TkMacOSXInitCGDrawing(
 CGImageRef
 TkMacOSXCreateCGImageFromDrawableRect(
     Drawable drawable,
+    int force_1x_scale,
     int x,
     int y,
     unsigned int width,
@@ -156,6 +162,9 @@ TkMacOSXCreateCGImageFromDrawableRect(
 	    CGImageRelease(cg_image);
 	}
     } else if ((view = TkMacOSXDrawableView(mac_drawable)) != nil) {
+	int scaleFactor = TkMacOSXNSWindowBackingScaleFactor(
+		[view window]);
+
 	/*
 	 * Convert Tk top-left to NSView bottom-left coordinates.
 	 */
@@ -192,6 +201,28 @@ TkMacOSXCreateCGImageFromDrawableRect(
 	sub_cg_image = [bitmap_rep CGImage];
 	CGImageRetain(sub_cg_image);
 	[bitmap_rep release];
+	if (force_1x_scale && (scaleFactor != 1)) {
+	    cg_image = sub_cg_image;
+	    sub_cg_image = NULL;
+	    // See http://blog.foundry376.com/2008/07/scaling-a-cgimage/#comment-200
+	    // create context, keeping original image properties
+	    CGColorSpaceRef colorspace = CGImageGetColorSpace(cg_image);
+	    cg_context = CGBitmapContextCreate(NULL, width, height,
+		    CGImageGetBitsPerComponent(cg_image),
+		    CGImageGetBytesPerRow(cg_image),
+		    colorspace,
+		    CGImageGetAlphaInfo(cg_image));
+	    CGColorSpaceRelease(colorspace);
+	    if (cg_context) {
+		// draw image to context (resizing it)
+		CGContextDrawImage(cg_context, CGRectMake(0, 0, width, height),
+			cg_image);
+		// extract resulting image from context
+		sub_cg_image = CGBitmapContextCreateImage(cg_context);
+		CGContextRelease(cg_context);
+	    }
+	    CGImageRelease(cg_image);
+	}
     } else {
 	TkMacOSXDbgMsg("Invalid source drawable");
     }
@@ -288,7 +319,8 @@ XCopyArea(
 	return BadDrawable;
     }
 
-    img = TkMacOSXCreateCGImageFromDrawableRect(src,
+    // Use unscaled source (TkMacOSXDrawCGImage() will implicitly downscale)
+    img = TkMacOSXCreateCGImageFromDrawableRect(src, 0,
 	    src_x, src_y, width, height);
 
     if (img) {
diff --git macosx/tkMacOSXImage.c macosx/tkMacOSXImage.c
index 0654825a5..855122e69 100644
--- macosx/tkMacOSXImage.c
+++ macosx/tkMacOSXImage.c
@@ -178,7 +178,7 @@ XGetImage(
 	}
 
 	CGImageRef cg_image = TkMacOSXCreateCGImageFromDrawableRect(
-		drawable, x, y, width, height);
+		drawable, 1, x, y, width, height);
 	if (!cg_image) {
 	    TkMacOSXDbgMsg("XGetImage: Failed to get CGImage");
 	    return NULL;
diff --git macosx/tkMacOSXPrivate.h macosx/tkMacOSXPrivate.h
index 2b71434d1..032f7443a 100644
--- macosx/tkMacOSXPrivate.h
+++ macosx/tkMacOSXPrivate.h
@@ -268,6 +268,7 @@ MODULE_SCOPE int	TkMacOSXIsWindowZoomed(TkWindow *winPtr);
 MODULE_SCOPE int	TkGenerateButtonEventForXPointer(Window window);
 MODULE_SCOPE EventModifiers TkMacOSXModifierState(void);
 MODULE_SCOPE CGImageRef	TkMacOSXCreateCGImageFromDrawableRect(Drawable drawable,
+			    int force_1x_scale,
 			    int x, int y, unsigned int width, unsigned int height);
 MODULE_SCOPE CGImageRef TkMacOSXCreateCGImageWithXImage(XImage *image);
 MODULE_SCOPE void       TkMacOSXDrawCGImage(Drawable d, GC gc, CGContextRef context,
-- 
2.28.0

