Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Win32] ImageDataProvider: incorrectly reported zoom level #1579

Open
tmssngr opened this issue Nov 8, 2024 · 12 comments
Open

[Win32] ImageDataProvider: incorrectly reported zoom level #1579

tmssngr opened this issue Nov 8, 2024 · 12 comments

Comments

@tmssngr
Copy link
Contributor

tmssngr commented Nov 8, 2024

Describe the bug
An ImageDataProvider can be used to provide different ImageData instances for the passed zoom level. This does not work correctly - the passed zoom level is incorrect. It "snaps" to multiples of 100. For 150% zoom level on Windows 11, I'm getting the value 100, for 175% zoom level I'm getting 200.

To Reproduce
Run this snippet on Windows 11 with different monitor zoom levels (I only have one 4k monitor attached):

import java.io.*;
import java.nio.file.*;

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class ImageDpiTest {

	public static void main(String[] args) throws IOException {
		final ImageData imageData1x;
		try (InputStream inputStream = Files.newInputStream(Paths.get("test1x.png"))) {
			imageData1x = new ImageData(inputStream);
		}

		final ImageData imageData2x;
		try (InputStream inputStream = Files.newInputStream(Paths.get("test2x.png"))) {
			imageData2x = new ImageData(inputStream);
		}

		final Display display = new Display();
		final Image image = new Image(display, (ImageDataProvider)zoom -> {
			System.out.println("zoom = " + zoom);
			return zoom >= 150 ? imageData2x : imageData1x;
		});

		final Shell shell = new Shell(display);
		shell.setLayout(new GridLayout(1, false));

		final Label label = new Label(shell, SWT.NORMAL);
		label.setImage(image);
		label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

		shell.setSize(400, 300);
		shell.open();

		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}

		display.dispose();
	}
}

It will log the reported zoom level.

Expected behavior
For 150% Windows/monitor zoom level, I expect 150 to be reported, for 175% I expect to get 175. Otherwise the image datas I report back are too small for 150% zoom level.

Screenshots
For 150% zoom level:
zoom-150% - 100
For 175% zoom level:
zoom-175% - 200

Environment:

  1. Select the platform(s) on which the behavior is seen:
    • All OS
    • Windows
    • Linux
    • macOS
  1. Additional OS info (e.g. OS version, Linux Desktop, etc)
    Tried on Windows 11 23H2 with one 32" 4k monitor attached.

Workaround (or) Additional context
Set swt.autoScale to false, but this will cause other problems.

@HeikoKlare
Copy link
Contributor

Expected behavior
For 150% Windows/monitor zoom level, I expect 150 to be reported, for 175% I expect to get 175. Otherwise the image datas I report back are too small for 150% zoom level.

Can you please explain where this expectation comes from? The behavior should have never been like this. Actually, the behavior of having the device zoom being "rounded" to multiples of 100 is intended as being established several years ago: https://eclipse.dev/eclipse/news/4.6/platform.php#swt-autoscale-tweaks

If the application is supposed to scale everything according to the actual native zoom (including images), you should use swt.autoScale=quarter or swt.autoScale=exact.

@tmssngr
Copy link
Contributor Author

tmssngr commented Nov 8, 2024

My expectation comes from the fact that numbers like 100 or 200 are used. If the rounding to 100 would be the goal, I would have expected the use of 1 and 2 (factor).

@tmssngr
Copy link
Contributor Author

tmssngr commented Nov 8, 2024

Background: until now we were using swt.autoScale=none because this gave us access to the full resolution on Windows with 4k monitors.

Is there also a mode where my solely my ImageDataProvider decides what image to use (without any magic auto-zooming under the hood)?

BTW, how I can request the actual shell's zoom level now?

@HeikoKlare
Copy link
Contributor

HeikoKlare commented Nov 8, 2024

I have to admit that I do not understand the actual issue: is there any behavior that has changed? With swt.autoScale=none, the zoom value should have always been rounded to a multiple of 100. none is not a valid value for swt.autoScale, so the code always defaults back to this:

if (zoom == 0) { // || "integer".equalsIgnoreCase (value) || "integer200".equalsIgnoreCase (value)
zoom = Math.max ((nativeDeviceZoom + 25) / 100 * 100, 100);
}

Also the image constructor retrieving the image data has always taken this device zoom, if I am not mistaken. So would be interesting to know where the behavior has actually changed in comparison to, e.g., the previous release of Eclipse SWT.

Is there also a mode where my solely my ImageDataProvider decides what image to use (without any magic auto-zooming under the hood)?

In the ImageDataProvider, you can do whatever you want. No one forces you to consider the zoom value passed to it. However, the question is how you want to provide properly scaled image data if you do not consider the context and its scaling. With the improved HiDPI support coming up (https://eclipse.dev/eclipse/news/4.34/platform.php#rescale-on-runtime-preference), you need to consider the context in which an image is used to adapt it to the zoom level of the monitor currently working on. If you do not want to support this kind of functionality in the future, you could fall back to, e.g., the non-API DPIUtil#getNativeDeviceZoom() to retrieve the original zoom value of the OS.

BTW, how I can request the actual shell's zoom level now?

A shell does not have any public API to retrieve its zoom. It has the getZoom() method to retrieve the zoom used by SWT (which in your scenario is 100/200/...) and it has nativeZoom with the original native zoom of the application, but none of them is accessible outside SWT. In which scenario do you need to retrieve the zoom of a shell? See comment below

@laeubi
Copy link
Contributor

laeubi commented Nov 8, 2024

A shell does not have any public API to retrieve its zoom.

Shell.getMonitor().getZoom() ?

@tmssngr
Copy link
Contributor Author

tmssngr commented Nov 8, 2024

In the ImageDataProvider, you can do whatever you want. No one forces you to consider the zoom value passed to it. However, the question is how you want to provide properly scaled image data if you do not consider the context and its scaling.

I've tried our application with the new SWT library with swt.autoScale set to quarter or exact. It caused auto-scaling for some images (e.g. in buttons), because there were some scaling artefacts visible, e.g. duplicate pixels where in the 100% and 200% images were just 1-pixel width.

@tmssngr
Copy link
Contributor Author

tmssngr commented Nov 15, 2024

What can I do to use our 200% zoom images for a system zoom level of 150%? How can I avoid any auto-scaling for the image datas provided by my ImageDataProvider?

@tmssngr
Copy link
Contributor Author

tmssngr commented Nov 15, 2024

Also the image constructor retrieving the image data has always taken this device zoom, if I am not mistaken. So would be interesting to know where the behavior has actually changed in comparison to, e.g., the previous release of Eclipse SWT.

We were using the swt.autoScale=false (sorry, my above mentioned none was the wrong value) on Windows since SWT began to supported HiDPI, because it looked better. Now I assume that the new monitor-specific zoom handling seems to require that we avoid this hack.

@HeikoKlare
Copy link
Contributor

What can I do to use our 200% zoom images for a system zoom level of 150%? How can I avoid any auto-scaling for the image datas provided by my ImageDataProvider?

From my understanding, for a system zoom of 150%, the 200% images will only be used when using quarter or exact mode (and then scaled down from 200% to 150%). If you are using integer200 or false, the effective application zoom is 100% and thus the 100% images are requested by SWT. But it should have always been like this. Auto-scaling of images is only performed if the image is not provided in the same zoom as required by the consumer, e.g., if you provide a 200% image while the application requests a 150% one. But if you are running your application with swt.autoScale=false, it should have never used the 200% images, has it?

We were using the swt.autoScale=false (sorry, my above mentioned none was the wrong value) on Windows since SWT began to supported HiDPI, because it looked better. Now I assume that the new monitor-specific zoom handling seems to require that we avoid this hack.

The new monitor-specific scaling should not affect the existing behavior in any way. It is supposed to be an opt-in feature (for now). So if anything works different for you then it did before, there is probably some regression that we missed so far. We actively test integer200 (as the default) and quarter (as the "reasonable") mode, so we may have missed some changes that unintentionally affected the false mode.

@tmssngr
Copy link
Contributor Author

tmssngr commented Nov 15, 2024

So SWT always performs auto-scaling for images (e.g. for zoom levels like 150% or 175%) and there is no way to avoid it?

@HeikoKlare
Copy link
Contributor

So SWT always performs auto-scaling for images (e.g. for zoom levels like 150% or 175%) and there is no way to avoid it?

It depends:

  • Using values quarter and exact for swt.autoScale, SWT will auto scale images for matching zoom levels like 150% or 175% (in case there is no according image present, as you may also provide a proper 150% image).
  • Using values like integer200 and false for swt.autoScale, there will usually be no auto scaling, as effectively zoom values will be limited to 100% and 200% with these settings (and with false even only 100%). Images are actually the reason for these scaling modes, as in general the quarter/exact scaling makes more sense but requires (rasterized) images to be rescaled. Once we support SVG render at runtime, that would become obsolete.

But as said: this is existing behavior of SWT for years, and none of the recent changes should have affected that.

@tmssngr
Copy link
Contributor Author

tmssngr commented Nov 15, 2024

How do I get the exact bounds of the loaded image (with swt.autoScale unset)? Note, that getBoundsInPixels() is deprecated and hence no allowed answer. ;)

Background: we are loading an image containing multiple sub-images:
directories
The code that extracts the subimages knows the subimage counts per row/column and hence asserts that bounds.width % columnCount == 0 and bounds.height % rowCount == 0.

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

No branches or pull requests

3 participants