Skip to content

Conversation

@fedejeanne
Copy link
Member

@fedejeanne fedejeanne commented Feb 21, 2025

Windows only: wse the original width and height of the images set to the shell when searching for the best fit instead of adapting them to the zoom, otherwise the smallest image will always be picked and it will result in a blurry shell icon when scaling up for monitors with a higher zoom levels.

contributes to
#62 and #127

How to test

  • Start the Runtime Application with Monitor-specific UI scaling active
  • Move it to a 200% monitor: the shell (window) icon should look sharper

Before this PR
image

After this PR
image

Is this a regression?

Yes Sort of (one needs to activate the experimental feature Monitor-specific UI scaling in order to experience this behavior), introduced when we started using the SMOOTH scaling method (instead of NEAREST) on Windows (df0ae71 / ffa1084).

As demonstration, here's how the shell icon looks like without this PR and using the NEAREST scaling method:

image

And this is how it looks like in 4.34 (identical):
image

Since reverting back to the NEAREST scaling method has other implications (and overall the scaling method SMOOTH is better), I still propose to accept this PR as a regression fix even though it technically doesn't restore the previous behavior but it makes it better.

@fedejeanne fedejeanne linked an issue Feb 21, 2025 that may be closed by this pull request
@fedejeanne fedejeanne force-pushed the fix_blurry_shell_icon_easy branch from b09e26a to 65e3fbf Compare February 21, 2025 08:32
@github-actions
Copy link
Contributor

github-actions bot commented Feb 21, 2025

Test Results

   505 files   -  1     505 suites   - 1   10m 6s ⏱️ + 1m 47s
 4 301 tests  - 37   4 289 ✅  - 35   11 💤  - 3  1 ❌ +1 
16 554 runs   - 37  16 447 ✅  - 35  106 💤  - 3  1 ❌ +1 

For more details on these failures, see this check.

Results for commit 67b2422. ± Comparison against base commit ccc69c2.

This pull request removes 37 tests.
org.eclipse.swt.graphics.ImageWin32Tests ‑ testImageDataForDifferentFractionalZoomsShouldBeDifferent
org.eclipse.swt.graphics.ImageWin32Tests ‑ testImageShouldHaveDimesionAsPerZoomLevel
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testByteArrayTransfer
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testFileTransfer
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testHtmlTransfer
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testImageTransfer_fromCopiedImage
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testImageTransfer_fromImage
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testImageTransfer_fromImageData
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testImageTransfer_fromImageDataFromImage
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testRtfTransfer
…

♻️ This comment has been updated with latest results.

datas = new ImageData [images.length];
for (int i=0; i<datas.length; i++) {
datas [i] = images [i].getImageData (getZoom());
datas [i] = images [i].getImageData ();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the getZoom call from here just calls getImageData(100), which doesn't change the ultimate effect of this. For example, even if you use getZoom() (e.g. 200) in this block and in the setImages you pass a small and a largeImage. If they have original size of 16 px and 32 px, it will still select smallImage of 16 px (32 px on 200) and largeImage of 32 px (64 px on 200). On debugging, I see it still allocates the image with the smallest size to smallIcon and the largest to largeIcon. I reckon the previous implementation is fine here.

I think the problem is rather in how you obtain the handle.

if (smallIcon != null) {
		switch (smallIcon.type) {
			case SWT.BITMAP:
				smallImage = Display.createIcon (smallIcon, getZoom());
				hSmallIcon = Image.win32_getHandle(smallImage, getZoom());
				break;
			case SWT.ICON:
				hSmallIcon = Image.win32_getHandle(smallIcon, getZoom());
				break;
		}
	}
	OS.SendMessage (handle, OS.WM_SETICON, OS.ICON_SMALL, hSmallIcon);
	if (largeIcon != null) {
		switch (largeIcon.type) {
			case SWT.BITMAP:
				largeImage = Display.createIcon (largeIcon, getZoom());
				hLargeIcon = Image.win32_getHandle(largeImage, getZoom());
				break;
			case SWT.ICON:
				hLargeIcon = Image.win32_getHandle(largeIcon, getZoom());
				break;
		}
	}
	OS.SendMessage (handle, OS.WM_SETICON, OS.ICON_BIG, hLargeIcon);

The call to Image::win32_getHandle will scale it down to the current zoom. Rather, we should use the correct zoom value for which the icon is supposed to be used. But the question is how the default image sizes which are passed in the method differ.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do see the effect tho, but I dont understand why data has to do anything with it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, odd. I do see a difference. I am printing the results by adding these lines

image

These are the outputs:
Before this PR

Zoom: 100
smallIcon bounds=Rectangle {0, 0, 16, 16}
largeIcon bounds=Rectangle {0, 0, 32, 32}
Zoom: 125
smallIcon bounds=Rectangle {0, 0, 16, 16}
largeIcon bounds=Rectangle {0, 0, 32, 32}
Zoom: 200
smallIcon bounds=Rectangle {0, 0, 16, 16}
largeIcon bounds=Rectangle {0, 0, 32, 32}

After this PR

Zoom: 100
smallIcon bounds=Rectangle {0, 0, 16, 16}
largeIcon bounds=Rectangle {0, 0, 32, 32}
Zoom: 125
smallIcon bounds=Rectangle {0, 0, 16, 16}
largeIcon bounds=Rectangle {0, 0, 48, 48}
Zoom: 200
smallIcon bounds=Rectangle {0, 0, 32, 32}
largeIcon bounds=Rectangle {0, 0, 48, 48}

As you can see, without this PR there is absolutely no difference in the output and with this PR, bigger icons are selected for higher zoom levels.

Copy link
Member Author

@fedejeanne fedejeanne Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do see the effect tho, but I dont understand why data has to do anything with it

My intention when using getImageMetadata() (without zoom) was to retrieve the "original" metadata i.e. the size for the 100% image. Doesn't the method retrieve that?

If I do this:

System.out.println("--- no zoom ---");
for (int i=0; i<images.length; i++) {
	ImageData imageData = images[i].getImageData();
	System.out.println("width=" + imageData.width + ", height=" + imageData.height + ", depth=" + imageData.depth);
}
System.out.println("--- getZoom() is " + getZoom() + " ---");
for (int i=0; i<images.length; i++) {
	ImageData imageData = images[i].getImageData(getZoom());
	System.out.println("width=" + imageData.width + ", height=" + imageData.height + ", depth=" + imageData.depth);
}

I see this:

--- no zoom ---
width=16, height=16, depth=24
width=32, height=32, depth=24
width=48, height=48, depth=24
--- getZoom() is 150 ---
width=24, height=24, depth=32
width=48, height=48, depth=32
width=72, height=72, depth=32

As you can see, the second set of sizes (and depth???) are scaled to 150%, which doesn't fit the requirements.

Am I seeing this wrong, @amartya4256 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't an icon that is 150% the height and width exactly what is expected at 150% zoom?
And the depth is not scaled to 150% (neither mathematically nor technically). It rather seems as if auto-scaling changes the image data type from some RGB type (24 bit) to some ARGB type (32 bit).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is what I expect but not what I need: I need to find the image whose original intended size (without rescaling) best matches the desired size in pixels. For example, if I have 2 images of sizes 16x16 and 32x32 and I want to create a shell icon of size 64x64 then I want to use the image that was created with a size of 32x32 (and scale it later).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what you mean by "original intended size". You always retrieve image data for a specific zoom, so the size of an image will depend on what zoom you pass to that method. If you want to know the logical size of an image (according to the auto-scaling capabilities), you may use image.getBounds().

Copy link
Contributor

@amartya4256 amartya4256 Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the depth discussion, here's the flow of the whole thing:

  1. setImages takes images [] array - usually 2 images in it - one small image, one large image
  2. If you call getImageData (with or without zoom), you can see the size difference as shown in Federico's logs.
  3. by default small icon has a size 16 px and large icon 32 px with better resolution - which means large icon looks more detailed.
  4. when you call getImageData(getZoom()) for both the image - let's say at 200%, it becomes 32 px and 64 px each.
  5. Compare method is called with the desired size information using getSystemMetrics (OS.SM_CXSMICON) and getSystemMetrics (OS.SM_CXICON), in a usual case it should be 16 and 32 px.
  6. getSystemMetrics (OS.SM_CXSMICON) is used to evaluate the small icon and getSystemMetrics (OS.SM_CXICON) is used to evaluate the large icon.
  7. If we pass the already scaled image data to sort method with getSystemMetrics (OS.SM_CXICON) to evaluate the large icon, it looks for the image with closest size to 32. Since small image's imaeg data is already 32, it doesn't choose the 64 (large) one but the small one's scaled imageData and it ends up looking pixelated at a higher zoom level.

For now, this fix is a good hack but since we are working with per dpi awareness, we should use the DPI aware system metrics (GetSystemMetricsForDPI) and find out a way to still obtain the large image instance for largeIcon. Anyway, the whole implementation of sort and compare method which uses loops to find out which one is big and which one is small looks quite old school and we can make it better design wise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to know the logical size of an image (according to the auto-scaling capabilities), you may use image.getBounds()

Now you hit the nail in the head! Thank you for hint, I'll look into it.

For now, this fix is a good hack...

I don't know why you guys keep calling it a hack, the method says that it returns...
image

...which is still a valid use case and a valid usage of the official API.

... we should use the DPI aware system metrics (GetSystemMetricsForDPI)...

How do you mean? getSystemMetrics(int) ends up calling OS::GetSystemMetricsForDpi

Anyway, the whole implementation of sort and compare method which uses loops to find out which one is big and which one is small looks quite old school and we can make it better design wise.

That's what I wanted to address in #1879 ;-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a deeper look into #1878 to understand what really happens here and left some comments there. From my current understanding, it would make sense to use getImageData(100) here, as the values are compared against 100% icon sizes, so why not explicitly retrieve the 100% data (and really doing that by passing 100 to avoid that the reader needs to understand the contract of parameter-less getImageData())?

@fedejeanne
Copy link
Member Author

I added some details about this PR fixing a regression and how I would like to proceed.

I still want to ask for PMC approval and merge this PR for RC2, accepting the fact that it technically doesn't restore the previous (blurry) icon but instead it fixes the original problem i.e. the fact that a small icon (16x16) is used for all zoom levels.

@akurtakov
Copy link
Member

@fedejeanne A procedural comment here - Please ask reviews from @eclipse-platform/eclipse-platform-project-leads and PMC . That way the former group will get notification thus better chances for review.
About the PR itself - I'm not in a position to review/test it thus can not approve it.

@fedejeanne
Copy link
Member Author

cc PMC @HannesWell

@HeikoKlare
Copy link
Contributor

Yes, this is a regression introduced when we started using the SMOOTH scaling method (instead of NEAREST) on Windows (df0ae71 / ffa1084).

We do not use SMOOTH scaling instead of NEAREST on Windows. The scaling mode is only changed automatically when monitor-specific scaling is used (which is still an experimental feature). Otherwise, one needs to set an according system property to enable SMOOTH scaling. So is this a regression limited to monitor-specific scaling or does it also affect default behavior of SWT / Eclipse RCP. And also the question arises whether this change affects default behavior as well, in particular, because this change does not fix the regression by reverting some change or the like but rather applies some improvement with behavior that has never been there like that before (so we may not know what effects this actually has, do we?). So wouldn't it be wise to do a proper fix for M1 instead of applying this quickfix in hope for improving an experimental feature but maybe also affecting default behavior in an unexpected way?

@fedejeanne
Copy link
Member Author

So wouldn't it be wise to do a proper fix for M1 instead of applying this quickfix in hope for improving an experimental feature but maybe also affecting default behavior in an unexpected way?

To quote you:

An experimental feature might not work perfectly fine anway (which is why it's flagged "experimental"), but improving it as far as possible for a release (such as with this change) improves the chance that people keep testing it and report back issues so that once delivering that feature as non-experimental and enabled-by-default, it will be in a better state.

@HeikoKlare
Copy link
Contributor

How is that quote related to this change? It refers to a PR that changes code which is only called when an experimental feature is active. This PR affects default behavior of SWT, as the code is executed in every SWT-based application.

@fedejeanne
Copy link
Member Author

fedejeanne commented Feb 24, 2025

It refers to a PR that changes code which is only called when an experimental feature is active.

That is the part I was missing, thank you. So the difference is about risk: I'm fine with pushing this to M1 then 👍

Windows only: use the original width and height of the images set to the
shell when searching for the best fit instead of adapting them to the
zoom, otherwise the smallest image will always be picked and it will
result in a blurry shell icon when scaling up for monitors with a higher
zoom levels.

contributes to
eclipse-platform#62 and
eclipse-platform#127
@fedejeanne fedejeanne force-pushed the fix_blurry_shell_icon_easy branch from 65e3fbf to 67b2422 Compare March 6, 2025 13:36
@fedejeanne
Copy link
Member Author

Test failure is unrelated #1843.
Freeze period is over too, I don't know why the check shows as "failed" but when I click on it, it says that it passed, which makes sense

@fedejeanne fedejeanne merged commit f30bf72 into eclipse-platform:master Mar 6, 2025
12 of 14 checks passed
@HeikoKlare HeikoKlare deleted the fix_blurry_shell_icon_easy branch March 6, 2025 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shell icon is blurry

4 participants