diff --git a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java index 6b4cb998c8..47b2045804 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java @@ -14,16 +14,19 @@ package org.eclipse.swt.graphics; import static org.junit.Assert.assertEquals; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; +import java.util.*; import java.util.concurrent.*; +import java.util.stream.*; import org.eclipse.swt.*; import org.eclipse.swt.internal.*; import org.eclipse.swt.widgets.*; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.params.*; +import org.junit.jupiter.params.provider.*; @ExtendWith(PlatformSpecificExecutionExtension.class) @ExtendWith(WithMonitorSpecificScalingExtension.class) @@ -142,4 +145,69 @@ private static int renderTextAndCountNonWhitePixels(Image target, Font font, Str } return count; } + + /** + * Regression test for the size calculation in scaling/cropping GC.drawImage() + * operations with asymmetric source dimensions (smaller height than width) at + * fractional zoom levels. + *

+ * At fractional zoom levels the effective X and Y scale factors diverge because + * each axis is rounded independently (e.g. at 125%: + * scaleFactorX = 625/500 = 1.25 but + * scaleFactorY = 24/19 ≈ 1.263). + */ + @ParameterizedTest + @MethodSource("zoomAndHeightArguments") + public void drawImage_asymmetricDimensionsAtFractionalZoom(int zoom, int height) { + Display display = Display.getDefault(); + + int logicalWidth = 500; + int logicalHeight = height; + + PaletteData palette = new PaletteData(0xFF0000, 0xFF00, 0xFF); + ImageData srcData = new ImageData(logicalWidth, logicalHeight, 32, palette); + for (int y = 0; y < logicalHeight; y++) { + for (int x = 0; x < logicalWidth; x++) { + // left half red, right half blue – makes wrong-rectangle errors visible + srcData.setPixel(x, y, x < logicalWidth / 2 ? 0xFF0000 : 0x0000FF); + } + } + Image srcImage = new Image(display, srcData); + + int previousZoom = DPIUtil.getDeviceZoom(); + try { + DPIUtil.setDeviceZoom(zoom); + + Image referenceImage = new Image(display, logicalWidth + 5, logicalHeight + 5); + GC referenceGC = new GC(referenceImage); + referenceGC.drawImage(srcImage, 0, 0); + referenceGC.dispose(); + + Image testImageScaled = new Image(display, logicalWidth + 5, logicalHeight + 5); + GC testGC = new GC(testImageScaled); + testGC.drawImage(srcImage, 0, 0, logicalWidth, logicalHeight); + testGC.dispose(); + assertArrayEquals(referenceImage.getImageData(zoom).data, testImageScaled.getImageData(zoom).data); + testImageScaled.dispose(); + + Image testImageScaledCropped = new Image(display, logicalWidth + 5, logicalHeight + 5); + testGC = new GC(testImageScaledCropped); + testGC.drawImage(srcImage, 0, 0, logicalWidth, logicalHeight, 0, 0, logicalWidth, logicalHeight); + testGC.dispose(); + assertArrayEquals(referenceImage.getImageData(zoom).data, testImageScaledCropped.getImageData(zoom).data); + testImageScaledCropped.dispose(); + + referenceImage.dispose(); + } finally { + DPIUtil.setDeviceZoom(previousZoom); + srcImage.dispose(); + } + } + + private static Stream zoomAndHeightArguments() { + int[] zooms = { 25, 50, 75, 100, 125, 150, 175, 200 }; + int[] heights = IntStream.rangeClosed(4, 20).toArray(); + return Arrays.stream(zooms).boxed() + .flatMap(zoom -> Arrays.stream(heights).mapToObj(height -> Arguments.of(zoom, height))); + } } 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 e6b99131a7..525cf88296 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 @@ -1250,11 +1250,15 @@ private Rectangle computeSourceRectangle(ImageHandle imageHandle, Rectangle full * computed to pixels depending on the factor of the full image bounds to the * actual OS handle size that will be used. */ - float scaleFactor = Math.min(1f * imageHandle.width() / fullImageBounds.width, 1f * imageHandle.height() / fullImageBounds.height); - int closestZoomOfHandle = Math.round(scaleFactor * 100); - Rectangle srcPixels = Win32DPIUtils.pointToPixel(drawable, src, closestZoomOfHandle); - - if (closestZoomOfHandle != 100) { + float scaleFactorX = (1f * imageHandle.width()) / fullImageBounds.width; + float scaleFactorY = (1f * imageHandle.height()) / fullImageBounds.height; + int srcXPixels = Math.round(scaleFactorX * src.x); + int srcWidthPixels = Math.round(scaleFactorX * (src.x + src.width)) - srcXPixels; + int srcYPixels = Math.round(scaleFactorY * src.y); + int srcHeightPixels = Math.round(scaleFactorY * (src.y + src.height)) - srcYPixels; + Rectangle srcPixels = new Rectangle(srcXPixels, srcYPixels, srcWidthPixels, srcHeightPixels); + + if (Math.abs(scaleFactorX - 1f) >= 0.01f || Math.abs(scaleFactorY - 1f) >= 0.01f) { /* * This is a HACK! Due to rounding errors at fractional scale factors, * the coordinates may be slightly off. The workaround is to restrict @@ -1263,7 +1267,7 @@ private Rectangle computeSourceRectangle(ImageHandle imageHandle, Rectangle full int errX = srcPixels.x + srcPixels.width - imageHandle.width(); int errY = srcPixels.y + srcPixels.height - imageHandle.height(); if (errX != 0 || errY != 0) { - if (errX <= closestZoomOfHandle / 100 && errY <= closestZoomOfHandle / 100) { + if (errX <= Math.max(1, scaleFactorX) && errY <= Math.max(1, scaleFactorY)) { srcPixels.intersect(new Rectangle(0, 0, imageHandle.width(), imageHandle.height())); } else { SWT.error (SWT.ERROR_INVALID_ARGUMENT);