Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 538487

Summary: [HiDPI] GC.copyArea() and Image.getImageData() behave differently (and buggy) MacOS 10.13.6.
Product: [Eclipse Project] Platform Reporter: Tim Mueller <legendaerer>
Component: SWTAssignee: Sravan Kumar Lakkimsetti <sravankumarl>
Status: RESOLVED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: ericwill, felix.hirsch, lshanmug, mariomarinato, p.beauvoir, rkd540
Version: 4.9   
Target Milestone: 4.11   
Hardware: All   
OS: Mac OS X   
See Also: https://git.eclipse.org/r/133059
https://git.eclipse.org/c/platform/eclipse.platform.swt.git/commit/?id=1fdceeecf03490cf104f153703ea774962a5e0a2
Whiteboard:
Bug Depends on:    
Bug Blocks: 538680, 541840    
Attachments:
Description Flags
Snippets To reproduce this issue none

Description Tim Mueller CLA 2018-08-31 12:51:23 EDT
GC.copyArea() and Image.getImageData() behave differently (and buggy) on HiDPI screens on Windows10 and MacOS 10.13.6. 

GC.copyArea(image, 0,0):
on Mac: makes a copy of the HiDPI screen and stores only the HiDPI (x2) version in the image. Not a downscaled x1 version. While this seems correct, it leads to the situation that if the image is drawn with less than 50% zoom with gc.drawImage(image, 0, 0) then it will draw no image at all. Probably because MacOS tries to draw or scale from the 100% x1 non-retina image which is not available in the image. Drawing with zoom>50% works fine.

on Windows: makes a copy of the HiDPI screen and works as expected while drawing with all zoom levels.

Image.getImageData() on the image captured with GC.copyArea:
on Mac: return an ImageData object with the amount of physical screen pixels. E.g. if MacOS horizontal resolution is 1600 on a Retina screen the number of physical pixels is 3200. Then the returned ImageData object also contains 3200 pixels.

on Windows: return an ImageData object with the amount of LOGICAL screen pixels. E.g. with 3200 physical screen pixels and screen zoom of 2 the screen has 1600 logical pixels. This is also the size of the returned ImageData object. There seems to be no way to access the full 3200 pixel resolution from an image.getImageData() as an ImageData object. With SWT 4.5.2 it behaved differently and returned the full resolution 3200 pixel ImageData object. SWT 4.7.3a, 4.8, 4.9RC1 all return only the low resolution image.

Proposed fixes:
1)GC.copyArea(image, 0,0) on Mac should also store a low resolution version in the supplied image to allow drawing with zoom levels <0.5. I think MacOS assumes the non-retina version is always available.
2)image.getImageData() on Windows should return the hiDPI version of the image (like it did up to SWT4.5.2), not the low resolution version, if the image is obtained by GC.copyArea().
Comment 1 Tim Mueller CLA 2018-08-31 14:27:44 EDT
I found out that the windows behavior of Image.getImageData() seems to be correct to return only the low resolution image with 100% zoom level. The Mac version returns the high resolution version which seems to be wrong, since javadoc says Image.getImageData(int zoom) should be used to obtain the image at different zoom levels. Since SWT operates on logical pixel instead of physical HiDpi pixels the windows behavior seems correct. 

If using Image.getImageData(200) on Mac, it even returns an image with 2 times the retina resolution => on Mac images seem to be scaled from the HiDpi version as the 100% basis, whereas on Windows the non-HiDpi version is used as 100% basis.

That means the proposed fix 2) should be changed to fix the Mac behavior to return the low resolution image for Image.getImageData() like it is done on Windows.

Also it would be good to know what is the current display zoom level and what maximum resolution is available for an image captured with GC.copyArea(). Currently I found only the system property org.eclipse.swt.internal.deviceZoom as an official way to get to know the zoom. Could we just get a normal API call instead?
Comment 2 Lakshmi P Shanmugam CLA 2018-09-12 08:45:37 EDT
(In reply to Tim Mueller from comment #0)
> GC.copyArea() and Image.getImageData() behave differently (and buggy) on
> HiDPI screens on Windows10 and MacOS 10.13.6. 
> 
> GC.copyArea(image, 0,0):
> on Mac: makes a copy of the HiDPI screen and stores only the HiDPI (x2)
> version in the image. Not a downscaled x1 version. While this seems correct,
> it leads to the situation that if the image is drawn with less than 50% zoom
> with gc.drawImage(image, 0, 0) then it will draw no image at all. Probably
> because MacOS tries to draw or scale from the 100% x1 non-retina image which
> is not available in the image. Drawing with zoom>50% works fine.
> 
Can you pls provide a snippet/sample code to reproduce this?

(In reply to Tim Mueller from comment #1)
> Also it would be good to know what is the current display zoom level and
> what maximum resolution is available for an image captured with
> GC.copyArea(). Currently I found only the system property
> org.eclipse.swt.internal.deviceZoom as an official way to get to know the
> zoom. Could we just get a normal API call instead?
Since 4.8, Monitor.getZoom() returns the zoom value for the monitor . You can get the monitor using Control.getMonitor().
Comment 3 Tim Mueller CLA 2018-09-19 14:27:34 EDT
Here is the code that I am using to take the screenshot on all platforms. It already contains code to work around the issue and obtain a screenshot with full resolution on all platforms:

GC screenGC = new GC(mw.display);
Image screenshot = new Image(mw.display, mw.display.getBounds());
mw.shell.setMinimized(true);
long time = System.currentTimeMillis();
while(System.currentTimeMillis()-time < millis){
  try {
  Thread.sleep(millis - (System.currentTimeMillis()-time));
   } catch (InterruptedException e) {}
				}
screenGC.copyArea(screenshot, 0, 0);
mw.shell.setMinimized(false);
screenGC.dispose();
 
// XXX on mac with SWT4.9 if the screenshot is taken from a HiDpi display and zoom < 0.5 during image painting there is no image available
// see Bug 538487
// therefore we copy the image and create a double size image
// this must be only enabled on HiDpi devices!!
if (HiDpiUtils.getDeviceZoomPercent()!=100){ 
   // HiDPI display, take the full resolution image and store it at 100% to allow access to all pixels
   ImageData imData = null;
   if (OSAdapter.isMac()){
   // work around Mac SWT bug to return full resolution with this call
   imData = screenshot.getImageData(); // on SWT 4.9RC1 this returns the full resolution on Mac
}else {
   imData = screenshot.getImageData(HiDpiUtils.getDeviceZoomPercent());
}
   screenshot.dispose();
   screenshot = new Image(mw.display, imData);
}

Hope this helps
Comment 4 Rajesh Dash CLA 2018-11-19 10:08:01 EST
(In reply to Tim Mueller from comment #3)
> Here is the code that I am using to take the screenshot on all platforms. It
> already contains code to work around the issue and obtain a screenshot with
> full resolution on all platforms:
> 
> GC screenGC = new GC(mw.display);
> Image screenshot = new Image(mw.display, mw.display.getBounds());
> mw.shell.setMinimized(true);
> long time = System.currentTimeMillis();
> while(System.currentTimeMillis()-time < millis){
>   try {
>   Thread.sleep(millis - (System.currentTimeMillis()-time));
>    } catch (InterruptedException e) {}
> 				}
> screenGC.copyArea(screenshot, 0, 0);
> mw.shell.setMinimized(false);
> screenGC.dispose();
>  
> // XXX on mac with SWT4.9 if the screenshot is taken from a HiDpi display
> and zoom < 0.5 during image painting there is no image available
> // see Bug 538487
> // therefore we copy the image and create a double size image
> // this must be only enabled on HiDpi devices!!
> if (HiDpiUtils.getDeviceZoomPercent()!=100){ 
>    // HiDPI display, take the full resolution image and store it at 100% to
> allow access to all pixels
>    ImageData imData = null;
>    if (OSAdapter.isMac()){
>    // work around Mac SWT bug to return full resolution with this call
>    imData = screenshot.getImageData(); // on SWT 4.9RC1 this returns the
> full resolution on Mac
> }else {
>    imData = screenshot.getImageData(HiDpiUtils.getDeviceZoomPercent());
> }
>    screenshot.dispose();
>    screenshot = new Image(mw.display, imData);
> }
> 
> Hope this helps

This Problem is reproducible an even the workaround does not work for me.Creating second image with image data  creates a empty image Though I am not suer here whether this workaround is foir mac or windows as  check with mac does nothing other then getting the imagedata

if (OSAdapter.isMac()){
>    // work around Mac SWT bug to return full resolution with this call
>    imData = screenshot.getImageData(); // on SWT 4.9RC1 this returns the

Attached the snippets where I could able to reproduce the problem
Comment 5 Rajesh Dash CLA 2018-11-19 10:10:40 EST
Created attachment 276615 [details]
Snippets To reproduce this issue

Getting imagedata and creating the image out of the imagedata produce white image always
Comment 6 Lakshmi P Shanmugam CLA 2018-11-23 03:43:33 EST
(In reply to Rajesh Dash from comment #4)
> 
> This Problem is reproducible an even the workaround does not work for
> me.Creating second image with image data  creates a empty image Though I am
> not suer here whether this workaround is foir mac or windows as  check with
> mac does nothing other then getting the imagedata
> 
> if (OSAdapter.isMac()){
> >    // work around Mac SWT bug to return full resolution with this call
> >    imData = screenshot.getImageData(); // on SWT 4.9RC1 this returns the
> 
> Attached the snippets where I could able to reproduce the problem

Which version of eclipse are you using?
I'm unable to reproduce the empty image problem with your snippet on 4.9 and mac OS 10.13.6. 
The problem I see is that the captured image is double the size.
Comment 7 Lakshmi P Shanmugam CLA 2018-11-23 03:53:07 EST
Sravan,
I think the changes to Image.getImageData(int) from https://git.eclipse.org/r/#/c/132283/ will fix the Image doubling problem, can you please verify?
Comment 8 Eclipse Genie CLA 2018-11-26 07:05:32 EST
New Gerrit change created: https://git.eclipse.org/r/133059
Comment 9 Sravan Kumar Lakkimsetti CLA 2018-11-28 05:45:13 EST
https://git.eclipse.org/r/133059
This is returning a blurry image. which is a no go. Moving to 4.11
Comment 10 Tim Mueller CLA 2019-01-30 14:05:46 EST
I have tested SWT 4.11M1 on MacOS Mojave (10.14) and the behavior is still the same:

screenGC.copyArea(screenshot, 0, 0);
imData = screenshot.getImageData();

=> image width = 3360

screenGC.copyArea(screenshot, 0, 0);
imData = imData = screenshot.getImageData(shell.getMonitor().getZoom());

=> image width = 6720
(shell.getMonitor().getZoom() = 200)
Comment 11 Sravan Kumar Lakkimsetti CLA 2019-02-01 04:34:34 EST
(In reply to Tim Mueller from comment #10)
> I have tested SWT 4.11M1 on MacOS Mojave (10.14) and the behavior is still
> the same:
> 
> screenGC.copyArea(screenshot, 0, 0);
> imData = screenshot.getImageData();
> 
> => image width = 3360
> 
> screenGC.copyArea(screenshot, 0, 0);
> imData = imData = screenshot.getImageData(shell.getMonitor().getZoom());
> 
> => image width = 6720
> (shell.getMonitor().getZoom() = 200)

This is expected as getImageData() works with 100% image, and getImageData(200) works with 200% image. So you'll find double size
Comment 12 Tim Mueller CLA 2019-02-01 11:23:34 EST
I thought screenshot.getImageData() should return an image with a width specified in logical pixels not physical pixels. 3360 is the width of the screen in physical pixels on a 15inch MacBook Pro Retina. 1680 would be the number of logical pixels.

As I wrote in the initial bug report, the behavior on windows 10 is different. There it will return the number of logical pixels.
Comment 13 Sravan Kumar Lakkimsetti CLA 2019-02-03 23:56:41 EST
(In reply to Tim Mueller from comment #12)
> I thought screenshot.getImageData() should return an image with a width
> specified in logical pixels not physical pixels. 3360 is the width of the
> screen in physical pixels on a 15inch MacBook Pro Retina. 1680 would be the
> number of logical pixels.
> 
> As I wrote in the initial bug report, the behavior on windows 10 is
> different. There it will return the number of logical pixels.

You are right. I will take a look. If you have snippet can you please attach to this bug?
Comment 14 Lakshmi P Shanmugam CLA 2019-02-11 07:31:03 EST
@Sravan,
Please create a separate patch to handle proper scaling of getImageData(200) on non-retina display.
Comment 15 Sravan Kumar Lakkimsetti CLA 2019-02-11 07:44:43 EST
I am pushing a patch today. Please check tomorrow's build and let me know if this fixes you issue
Comment 17 Sravan Kumar Lakkimsetti CLA 2019-02-11 08:03:32 EST
(In reply to Lakshmi Shanmugam from comment #14)
> @Sravan,
> Please create a separate patch to handle proper scaling of getImageData(200)
> on non-retina display.

Raised bug 544341 to address this
Comment 18 Sravan Kumar Lakkimsetti CLA 2019-02-11 08:03:57 EST
(In reply to Eclipse Genie from comment #16)
> Gerrit change https://git.eclipse.org/r/133059 was merged to [master].
> Commit:
> http://git.eclipse.org/c/platform/eclipse.platform.swt.git/commit/
> ?id=1fdceeecf03490cf104f153703ea774962a5e0a2

Merged to master
Comment 19 Mario Marinato CLA 2023-02-13 07:22:36 EST
This is bug is back.  On Eclipse 2022-12, the snippet generates white images.
As far as I can tell, it's been like this since SWT 4.18.
Comment 20 Phil Beauvoir CLA 2023-02-14 03:34:18 EST
(In reply to Mario Marinato from comment #19)
> This is bug is back.  On Eclipse 2022-12, the snippet generates white images.
> As far as I can tell, it's been like this since SWT 4.18.

It might be better if you open a new bug at https://github.com/eclipse-platform/eclipse.platform.swt/issues and reference this one. If you can add a Snippet to the bug as well.
Comment 21 Mario Marinato CLA 2023-02-14 05:24:17 EST
I'll sure do, Phil.  Thanks for the info.