Skip to content

Commit 35a7cb5

Browse files
committed
Fix GC#drawImage + ImageGcDrawer for Cropping and Scaling
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 vi-eclipse/Eclipse-Platform#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 width/height of the full image scaled by the scaledImageZoom. This is a space on where scaled src coordinates/width/height lie. A callback then creates a new handle for the height/width and respects 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. Additionally, if a transform is applied via the GC, then both drawImage APIs consider now the best fitting handle by requesting it through the width/height of the full image scaled by the scaleFactor (relating destination width/height to source width/height) times the width/height scaling induced by the transformation.
1 parent f005865 commit 35a7cb5

1 file changed

Lines changed: 48 additions & 27 deletions

File tree

  • bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@
1515

1616

1717
import java.util.*;
18-
import java.util.List;
1918
import java.util.function.*;
20-
import java.util.stream.*;
2119

2220
import org.eclipse.swt.*;
2321
import org.eclipse.swt.graphics.Image.*;
2422
import org.eclipse.swt.internal.*;
2523
import org.eclipse.swt.internal.gdip.*;
2624
import org.eclipse.swt.internal.win32.*;
27-
import org.eclipse.swt.widgets.*;
2825

2926
/**
3027
* Class <code>GC</code> is where all of the drawing capabilities that are
@@ -1183,15 +1180,6 @@ void apply() {
11831180
drawImage(getImage(), source.x, source.y, source.width, source.height, destination.x, destination.y, destination.width, destination.height, gcZoom, srcImageZoom);
11841181
}
11851182

1186-
private Collection<Integer> getAllCurrentMonitorZooms() {
1187-
if (device instanceof Display display) {
1188-
return Arrays.stream(display.getMonitors())
1189-
.map(Monitor::getZoom)
1190-
.collect(Collectors.toSet());
1191-
}
1192-
return Collections.emptySet();
1193-
}
1194-
11951183
private int calculateZoomForImage(int gcZoom, int srcWidth, int srcHeight, int destWidth, int destHeight) {
11961184
if (srcWidth == 1 && srcHeight == 1) {
11971185
// One pixel images can use the GC zoom
@@ -1207,13 +1195,7 @@ private int calculateZoomForImage(int gcZoom, int srcWidth, int srcHeight, int d
12071195

12081196
float imageScaleFactor = 1f * destWidth / srcWidth;
12091197
int imageZoom = Math.round(gcZoom * imageScaleFactor);
1210-
if (getAllCurrentMonitorZooms().contains(imageZoom)) {
1211-
return imageZoom;
1212-
}
1213-
if (imageZoom > 150) {
1214-
return 200;
1215-
}
1216-
return 100;
1198+
return imageZoom;
12171199
}
12181200
}
12191201

@@ -1232,37 +1214,76 @@ void apply() {
12321214
}
12331215
}
12341216

1217+
private float calculateTransformationScale() {
1218+
Transform current = new Transform(device);
1219+
getTransform(current);
1220+
float[] m = new float[6];
1221+
current.getElements(m);
1222+
float scaleWidth = (float) Math.hypot(m[0], m[2]);
1223+
float scaleHeight = (float) Math.hypot(m[1], m[3]);
1224+
return Math.max(scaleWidth, scaleHeight);
1225+
}
1226+
12351227
private void drawImage(Image image, int destX, int destY, int destWidth, int destHeight, int imageZoom) {
1236-
Rectangle destPixels = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX, destY, destWidth, destHeight),
1228+
float transformationScale = calculateTransformationScale();
1229+
int scaledImageZoomWithTransform = Math.round(transformationScale * imageZoom);
1230+
Rectangle destPixelsScaledWithTransform = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX , destY, destWidth , destHeight),
1231+
scaledImageZoomWithTransform);
1232+
Rectangle destPixels= Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX , destY, destWidth , destHeight),
12371233
imageZoom);
12381234
image.executeOnImageHandleAtBestFittingSize(tempHandle -> {
12391235
drawImage(image, 0, 0, tempHandle.getWidth(), tempHandle.getHeight(), destPixels.x, destPixels.y,
12401236
destPixels.width, destPixels.height, false, tempHandle);
1241-
}, destPixels.width, destPixels.height);
1237+
}, destPixelsScaledWithTransform.width, destPixelsScaledWithTransform.height);
12421238
}
12431239

12441240
private void drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY,
12451241
int destWidth, int destHeight, int imageZoom, int scaledImageZoom) {
12461242
Rectangle src = Win32DPIUtils.pointToPixel(drawable, new Rectangle(srcX, srcY, srcWidth, srcHeight), scaledImageZoom);
12471243
Rectangle dest = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX, destY, destWidth, destHeight), imageZoom);
1244+
Rectangle fullImageBounds = image.getBounds();
1245+
Rectangle fullImageBoundsScaled = Win32DPIUtils.pointToPixel(drawable, fullImageBounds, scaledImageZoom);
1246+
float transformationScale = calculateTransformationScale();
1247+
int scaledImageZoomWithTransform = Math.round(transformationScale * scaledImageZoom);
1248+
Rectangle fullImageBoundsScaledWithTransform = Win32DPIUtils.pointToPixel(drawable, fullImageBounds, scaledImageZoomWithTransform);
1249+
Rectangle unscaledSrc = new Rectangle(srcX, srcY, srcWidth, srcHeight);
12481250
if (scaledImageZoom != 100) {
12491251
/*
12501252
* This is a HACK! Due to rounding errors at fractional scale factors,
12511253
* the coordinates may be slightly off. The workaround is to restrict
12521254
* coordinates to the allowed bounds.
12531255
*/
1254-
Rectangle b = image.getBounds(scaledImageZoom);
1255-
int errX = src.x + src.width - b.width;
1256-
int errY = src.y + src.height - b.height;
1256+
int errX = src.x + src.width - fullImageBoundsScaled.width;
1257+
int errY = src.y + src.height - fullImageBoundsScaled.height;
12571258
if (errX != 0 || errY != 0) {
12581259
if (errX <= scaledImageZoom / 100 && errY <= scaledImageZoom / 100) {
1259-
src.intersect(b);
1260+
src.intersect(fullImageBoundsScaled);
12601261
} else {
12611262
SWT.error (SWT.ERROR_INVALID_ARGUMENT);
12621263
}
12631264
}
12641265
}
1265-
drawImage(image, src.x, src.y, src.width, src.height, dest.x, dest.y, dest.width, dest.height, false, image.getHandle(scaledImageZoom, data.nativeZoom));
1266+
image.executeOnImageHandleAtBestFittingSize((tempHandle) -> {
1267+
Rectangle newSrc = computeSourceRectangle(tempHandle, fullImageBounds, fullImageBoundsScaled, unscaledSrc, src);
1268+
drawImage(image, newSrc.x, newSrc.y, newSrc.width, newSrc.height, dest.x, dest.y, dest.width,
1269+
dest.height, false, tempHandle);
1270+
}, fullImageBoundsScaledWithTransform.width, fullImageBoundsScaledWithTransform.height);
1271+
}
1272+
1273+
private Rectangle computeSourceRectangle(ImageHandle imageHandle, Rectangle fullImageBounds, Rectangle fullImageBoundsScaled, Rectangle unscaledSrc, Rectangle src) {
1274+
if (new Rectangle(0, 0, imageHandle.getWidth(), imageHandle.getHeight()).equals(fullImageBoundsScaled)) {
1275+
return src;
1276+
} else {
1277+
/*
1278+
* the achieved handle with its drawings has not the required size, thus we calculate the zoom of the handle
1279+
*
1280+
* with respect to the full 100% image. The point values (x,y,width,height) of the source "part" of the full image will
1281+
* be computed to pixels by this zoom.
1282+
*/
1283+
float scaleFactor = 1f * imageHandle.getWidth() / fullImageBounds.width;
1284+
int closestZoomOfHandle = Math.round(scaleFactor * 100);
1285+
return Win32DPIUtils.pointToPixel(drawable, unscaledSrc, closestZoomOfHandle);
1286+
}
12661287
}
12671288

12681289
private class DrawImageToImageOperation extends ImageOperation {
@@ -5810,7 +5831,7 @@ void apply() {
58105831
* Returns the extent of the given string. No tab
58115832
* expansion or carriage return processing will be performed.
58125833
* <p>
5813-
* The <em>extent</em> of a string is the width and height of
5834+
* The <em>extent</em> ofbut a string is the width and height of
58145835
* the rectangular area it would cover if drawn in a particular
58155836
* font (in this case, the current font in the receiver).
58165837
* </p>

0 commit comments

Comments
 (0)