Skip to content

[win32] Choose best Image handle drawing with GC #2134

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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

akoch-yatta
Copy link
Contributor

This PR adapts the drawImage method in GC in the windows implementation for copying and (probably) scaling an area from a source image into the GC target. With the Image supporting multiple handles now, the handle from the source image is no longer chosen by the zoom context of the GC but from the target size, e.g. if an image should be drawn with a scale of 2 on a 100% monitor, it might provide better results to use an image handle fitting for 200%.

Example use case for this method is e.g. in GEF, where the internal GEF zoom is used to scaled images drawn in a diagram.

The PR depends on #2120 being merged first.

How it looks

This is one exempt of the Snippet below
Each row is a different image, e.g. created via ImageData, ImageFileNameProvider, ImageDataProvider, etc. and the greyed and disabled variants.
Left column is the code in the PR, middle is the previous method, right is enabling advanced mode and using the previous method (just for comparison)
This does not use any SVG. With those the result will be of course way better. The screenshot is using the PNGs from Snippet382.
Example

How to test

A small Snippet to see the different variants is elow. Prerequisite is to copy the old implementation into GC under a different name:

public void drawImage2 (Image image, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight) {
	if (handle == 0) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
	if (srcWidth == 0 || srcHeight == 0 || destWidth == 0 || destHeight == 0) return;
	if (srcX < 0 || srcY < 0 || srcWidth < 0 || srcHeight < 0 || destWidth < 0 || destHeight < 0) {
		SWT.error (SWT.ERROR_INVALID_ARGUMENT);
	}
	if (image == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
	if (image.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);

	int imageZoom = getZoom();
	float imageScaleFactor = 1f*destWidth/srcWidth;

	Rectangle src = DPIUtil.scaleUp(drawable, new Rectangle(srcX, srcY, srcWidth, srcHeight), imageZoom);
	Rectangle dest = DPIUtil.scaleUp(drawable, new Rectangle(destX, destY, destWidth, destHeight), imageZoom);

	if (imageZoom != 100) {
		/*
		 * This is a HACK! Due to rounding errors at fractional scale factors,
		 * the coordinates may be slightly off. The workaround is to restrict
		 * coordinates to the allowed bounds.
		 */
		Rectangle b = image.getBounds(imageZoom);
		int errX = src.x + src.width - b.width;
		int errY = src.y + src.height - b.height;
		if (errX != 0 || errY != 0) {
			if (errX <= imageZoom / 100 && errY <= imageZoom / 100) {
				src.intersect(b);
			} else {
				SWT.error (SWT.ERROR_INVALID_ARGUMENT);
			}
		}
	}
	drawImage(image, src.x, src.y, src.width, src.height, dest.x, dest.y, dest.width, dest.height, false, imageZoom);
}

Snippet:

package org.eclipse.swt.snippets;

import java.util.function.*;

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

public class Snippet384 {
	private static final String IMAGE_100 = "eclipse16.png";
	private static final String IMAGE_200 = "eclipse32.png";
	private static final String IMAGES_ROOT = "bin/org/eclipse/swt/snippets/";

	private static final String IMAGE_PATH_100 = IMAGES_ROOT + IMAGE_100;
	private static final String IMAGE_PATH_200 = IMAGES_ROOT + IMAGE_200;

	public static void main (String [] args) {
		final ImageFileNameProvider filenameProvider = zoom -> {
			switch (zoom) {
			case 100:
				return IMAGE_PATH_100;
			case 200:
				return IMAGE_PATH_200;
			default:
				return null;
			}
		};
		final ImageDataProvider imageDataProvider = zoom -> {
			switch (zoom) {
			case 100:
				return new ImageData (IMAGE_PATH_100);
			case 200:
				return new ImageData (IMAGE_PATH_200);
			default:
				return null;
			}
		};

		final Display display = new Display ();

		final Image imageWithFileNameProvider = new Image (display, filenameProvider);
		final Supplier<Image> disabledImageWithFileNameProvider = () ->  new Image (display,imageWithFileNameProvider, SWT.IMAGE_DISABLE);
		final Supplier<Image> greyImageWithFileNameProvider = () ->  new Image (display,imageWithFileNameProvider, SWT.IMAGE_GRAY);

		final Image imageWithDataProvider = new Image (display, imageDataProvider);
		final Image disabledImageWithDataProvider = new Image (display,imageWithDataProvider, SWT.IMAGE_DISABLE);
		final Image greyImageWithDataProvider = new Image (display,imageWithDataProvider, SWT.IMAGE_GRAY);

		final Image imageWithData = new Image (display, IMAGE_PATH_100);
		final Image disabledImageWithData = new Image (display,imageWithData, SWT.IMAGE_DISABLE);
		final Image greyImageWithData = new Image (display,imageWithData, SWT.IMAGE_GRAY);

		final Supplier<Image> imageWithDataCopy = () ->   new Image (display, imageWithDataProvider, SWT.IMAGE_COPY);
		final Supplier<Image> disabledImageWithDataCopy = () ->  new Image (display,disabledImageWithDataProvider, SWT.IMAGE_COPY);
		final Supplier<Image> greyImageWithDataCopy = () ->  new Image (display,greyImageWithDataProvider, SWT.IMAGE_COPY);

		final Shell shell = new Shell (display);
		shell.setText("Snippet384");
		shell.setLayout (new GridLayout (3, false));
		Listener l = new Listener() {
			@Override
			public void handleEvent(Event e)  {
				if (e.type == SWT.Paint) {
					GC mainGC = e.gc;
					GCData gcData = mainGC.getGCData();
					gcData.nativeZoom = 200;

					try {
						int baseY = 50;
						int itemCount = 1;
						drawText(mainGC, gcData,"--ImageFileNameProvider--", baseY*itemCount++);
						drawImages(mainGC, gcData, "Normal",baseY*itemCount++, imageWithFileNameProvider);
						drawImages(mainGC, gcData, "Disabled",baseY*itemCount++, disabledImageWithFileNameProvider.get());
						drawImages(mainGC, gcData, "Greyed",baseY*itemCount++, greyImageWithFileNameProvider.get());

						drawText(mainGC, gcData,"--ImageDataProvider--", baseY*itemCount++);
						drawImages(mainGC, gcData, "Normal",baseY*itemCount++, imageWithDataProvider);
						drawImages(mainGC, gcData, "Disabled",baseY*itemCount++, disabledImageWithDataProvider);
						drawImages(mainGC, gcData, "Greyed",baseY*itemCount++, greyImageWithDataProvider);

						drawText(mainGC, gcData,"--Image with ImageData--", baseY*itemCount++);
						drawImages(mainGC, gcData, "Normal",baseY*itemCount++, imageWithData);
						drawImages(mainGC, gcData, "Disabled",baseY*itemCount++, disabledImageWithData);
						drawImages(mainGC, gcData, "Greyed",baseY*itemCount++, greyImageWithData);

						drawText(mainGC, gcData,"--ImageDataProvider Copy--", baseY*itemCount++);
						drawImages(mainGC, gcData, "Normal",baseY*itemCount++, imageWithDataCopy.get());
						drawImages(mainGC, gcData, "Disabled",baseY*itemCount++, disabledImageWithDataCopy.get());
						drawImages(mainGC, gcData, "Greyed",baseY*itemCount++, greyImageWithDataCopy.get());
					} finally {
						mainGC.dispose ();
					}
				}
			}

			private void drawText(GC mainGC, GCData gcData, String text, int y) {
				mainGC.drawText(text, 20, y);
			}

			private void drawImages(GC mainGC, GCData gcData, String text, int y, final Image image) {
				drawImages(mainGC, 50, y, image, 1f);
				drawImages(mainGC, 200, y, image, 1.5f);
				drawImages(mainGC, 350, y, image, 2.25f);
			}

			private void drawImages(GC mainGC, int x,  int y, final Image image, float factor) {
				int width = image.getBounds().width;
				int height = image.getBounds().height;
				int scaledWidth = Math.round(width*factor);
				int scaledHeight = Math.round(height*factor);
				mainGC.drawImage(image, 0, 0, width, height, x, y, scaledWidth, scaledHeight);
				mainGC.drawImage2(image, 0, 0, width, height, x + scaledWidth, y, scaledWidth, scaledHeight);
				mainGC.setAdvanced(true);
				mainGC.drawImage2(image, 0, 0, width, height, x + (scaledWidth*2), y, scaledWidth, scaledHeight);
				mainGC.setAdvanced(false);
			}
		};
		shell.addListener(SWT.Paint, l);

		shell.setSize(400, 750);
		shell.open ();
		while (!shell.isDisposed ()) {
			if (!display.readAndDispatch ()) display.sleep ();
		}
		display.dispose ();
	}
}

Copy link
Contributor

github-actions bot commented May 8, 2025

Test Results

   545 files  + 6     545 suites  +6   30m 21s ⏱️ +38s
 4 377 tests +37   4 359 ✅ +35   18 💤 +3  0 ❌  - 1 
16 647 runs  +37  16 506 ✅ +35  141 💤 +3  0 ❌  - 1 

Results for commit 512c3a9. ± Comparison against base commit 4df99ba.

♻️ This comment has been updated with latest results.

@akoch-yatta
Copy link
Contributor Author

As a remark just for further explanation. This PR is calculating the exact zoom, so the image handle perfectly fits the destination width and height. Therefor it will directly scale the image internally, if there is no perfect match available. The scaling capabilities inside the GC implementation is skipped by that.
One step in between could be to still try to find the best matching handle without forcing the exact zoom, e.g.:
Setup is a 100% monitor and an image with being drawn with factor 3. This image is provided with an ImageFileNameProvider that has images for 100% and 200%. With the proposed approached the image handle will be created with 300%, the alternative could be to create an handle with 200% and it will be scaled by the GC to 300%. The upper screenshot is showing this implicitly be forcing the GC to use 200%.

This commit adapts the drawImage method in GC in the windows
implementation for copying and (probably) scaling an area from a source
image into the GC target. With the Image supporting multiple handles now,
the handle from the source image is no longer chosen by the zoom context
of the GC but from the target size, e.g. if an image should be drawn
with a scale of 2 on a 100% monitor, it might provide better results to
use an image handle fitting for 200%.
@akoch-yatta akoch-yatta force-pushed the win32-use-best-image-handle-for-gc-drawing branch from 4f8b1a3 to 512c3a9 Compare May 8, 2025 18:07
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.

1 participant