From 53c83c8373e1cc18f09763394ec3167c742fad31 Mon Sep 17 00:00:00 2001 From: al-noori Date: Tue, 9 Dec 2025 16:28:58 +0100 Subject: [PATCH] Fix GC#drawImage + ImageGcDrawer for Cropping and Scaling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GC#drawImage method takes (image, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight) as arguments and crops and scales from the source region to the destination region. Passing an image drawn via ImageGCDrawer led to the following issue: The image handle from the subcall is resolved using the monitor zoom (data.nativeZoom) and the calculated scaledImageZoom (gcZoom * scaleFactor). This handle corresponds to an ImageData initialized at scaledImageZoom, whereas the drawings of the second GC are performed using the monitor zoom, subject to the auto-scale property. This mismatch results in unaligned sizing of drawings. For example, a 200% monitor zoom combined with a scale factor of 0.5 produces a scaledImageZoom of 100%. As a result, the ImageData is initialized at 100%, while drawing occurs at 200%. This exact case is demonstrated in https://github.com/vi-eclipse/Eclipse-Platform/issues/554 . Furthermore, the calculation of scaledImageZoom uses fallback logic that only allows 100% and 200% as possible outcomes, which is clearly unintended in this context. The fix delegates resolving the correct handle to the Image class by passing the calculated scaledImageZoom. A callback then creates a new handle for this zoom, respecting the auto-scale property. If the returned handle matches the full image scaled to the requested scaledImageZoom in width and height, the source region coordinates/width/height are passed directly in pixels at that zoom. Otherwise, the internal zoom factor is derived from the returned handle’s width relative to the full image, and the source region coordinates are converted to pixel values using this internal zoom. --- .../win32/org/eclipse/swt/graphics/GC.java | 44 ++++++++++++------- .../win32/org/eclipse/swt/graphics/Image.java | 20 +++++++++ .../org/eclipse/swt/snippets/Snippet10.java | 14 +++--- 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java index e9fd4140349..089f87cc051 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java @@ -1183,15 +1183,6 @@ void apply() { drawImage(getImage(), source.x, source.y, source.width, source.height, destination.x, destination.y, destination.width, destination.height, gcZoom, srcImageZoom); } - private Collection getAllCurrentMonitorZooms() { - if (device instanceof Display display) { - return Arrays.stream(display.getMonitors()) - .map(Monitor::getZoom) - .collect(Collectors.toSet()); - } - return Collections.emptySet(); - } - private int calculateZoomForImage(int gcZoom, int srcWidth, int srcHeight, int destWidth, int destHeight) { if (srcWidth == 1 && srcHeight == 1) { // One pixel images can use the GC zoom @@ -1207,13 +1198,7 @@ private int calculateZoomForImage(int gcZoom, int srcWidth, int srcHeight, int d float imageScaleFactor = 1f * destWidth / srcWidth; int imageZoom = Math.round(gcZoom * imageScaleFactor); - if (getAllCurrentMonitorZooms().contains(imageZoom)) { - return imageZoom; - } - if (imageZoom > 150) { - return 200; - } - return 100; + return imageZoom; } } @@ -1243,6 +1228,7 @@ private void drawImage(Image image, int destX, int destY, int destWidth, int des private void drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, int imageZoom, int scaledImageZoom) { + Rectangle src = Win32DPIUtils.pointToPixel(drawable, new Rectangle(srcX, srcY, srcWidth, srcHeight), scaledImageZoom); Rectangle dest = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX, destY, destWidth, destHeight), imageZoom); if (scaledImageZoom != 100) { @@ -1262,7 +1248,31 @@ private void drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHei } } } - drawImage(image, src.x, src.y, src.width, src.height, dest.x, dest.y, dest.width, dest.height, false, image.getHandle(scaledImageZoom, data.nativeZoom)); + Rectangle fullImageBounds = image.getBounds(); + Rectangle targetSrc = Win32DPIUtils.pointToPixel(drawable, fullImageBounds, scaledImageZoom); + Rectangle startSrc = new Rectangle(srcX, srcY, srcWidth, srcHeight); + image.executeOnImageHandleAtBestFittingSizeAtZoom((tempHandle) -> { + Rectangle src1 = computeSourceRectangle(tempHandle, fullImageBounds, targetSrc, startSrc, src); + drawImage(image, src1.x, src1.y, src1.width, src1.height, dest.x, dest.y, dest.width, + dest.height, false, tempHandle); + }, scaledImageZoom); + +} + +private Rectangle computeSourceRectangle(ImageHandle imageHandle, Rectangle fullImageBounds, Rectangle targetSrc, Rectangle startSrc, Rectangle srcPart) { + if (new Rectangle(0, 0, imageHandle.getWidth(), imageHandle.getHeight()).equals(targetSrc)) { + return srcPart; + } else { + /* + * the achieved handle with its drawings has not the required size, thus we calculate the zoom of the handle + * + * with respect to the full 100% image. The point values (x,y,width,height) of the source "part" of the full image will + * be computed to pixels by this zoom. + */ + float scaleFactor = 1f * imageHandle.getWidth() / fullImageBounds.width; + int closestZoomOfHandle = Math.round(scaleFactor * 100); + return Win32DPIUtils.pointToPixel(drawable, startSrc, closestZoomOfHandle); + } } private class DrawImageToImageOperation extends ImageOperation { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java index a580d24c3c2..a8ee2c86491 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java @@ -179,6 +179,17 @@ private Optional createHandleAtExactSize(int width, int height) { return Optional.empty(); } + public ImageHandle getOrCreateImageHandleAtClosestSizeAtZoom(int scaledZoom) { + scaledZoom = DPIUtil.getZoomForAutoscaleProperty(scaledZoom); + ImageHandle bestFittingHandle = zoomLevelToImageHandle.get(scaledZoom); + if (bestFittingHandle == null) { + ImageData bestFittingImageData = imageProvider.loadImageData(scaledZoom).element(); + ImageData adaptedData = adaptImageDataIfDisabledOrGray(bestFittingImageData); + bestFittingHandle = init(adaptedData, -1); + } + return bestFittingHandle; + } + private ImageHandle getOrCreateImageHandleAtClosestSize(int widthHint, int heightHint) { Rectangle bounds = getBounds(100); int imageZoomForWidth = 100 * widthHint / bounds.width; @@ -880,6 +891,15 @@ void executeOnImageHandleAtBestFittingSize(Consumer handleAtSizeCon handleAtSizeConsumer.accept(imageHandle); } +void executeOnImageHandleAtBestFittingSizeAtZoom(Consumer handleAtSizeConsumer, int scaledZoom) { + ImageHandle imageHandle = lastRequestedHandle.getOrCreateImageHandleAtClosestSizeAtZoom(scaledZoom); + handleAtSizeConsumer.accept(imageHandle); +} +void executeOnImageHandleAtSizeOrZoom(BiConsumer handleAtSizeConsumer, int targetWidth, int targetHeight, int zoom) { + ImageHandle imageHandle = lastRequestedHandle.getOrCreateImageHandleAtClosestSizeAtZoom(zoom); + handleAtSizeConsumer.accept(imageHandle, new Point(imageHandle.getWidth(), imageHandle.getHeight())); +} + /** * IMPORTANT: This method is not part of the public * API for Image. It is marked public only so that it diff --git a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet10.java b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet10.java index bedaca7f4f2..31138fe18a7 100644 --- a/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet10.java +++ b/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet10.java @@ -32,18 +32,18 @@ public static void main(String[] args) { shell.setText("Advanced Graphics"); FontData fd = shell.getFont().getFontData()[0]; final Font font = new Font(display, fd.getName(), 60, SWT.BOLD | SWT.ITALIC); - final Image image = new Image(display, 640, 480); - final Rectangle rect = image.getBounds(); - GC gc = new GC(image); - gc.setBackground(display.getSystemColor(SWT.COLOR_RED)); - gc.fillOval(rect.x, rect.y, rect.width, rect.height); - gc.dispose(); + ImageGcDrawer igc = (gc, w, h) -> { + gc.setBackground(display.getSystemColor(SWT.COLOR_RED)); + gc.fillOval(0, 0, w, h); + }; + Image image = new Image(display, igc, 640, 480); + Rectangle rect = image.getBounds(); shell.addListener(SWT.Paint, event -> { GC gc1 = event.gc; Transform tr = new Transform(display); tr.translate(50, 120); tr.rotate(-30); - gc1.drawImage(image, 0, 0, rect.width, rect.height, 0, 0, rect.width / 2, rect.height / 2); + gc1.drawImage(image, 0, 0, rect.width / 2, rect.height / 2); gc1.setAlpha(100); gc1.setTransform(tr); Path path = new Path(display);