Skip to content

Commit af9dd04

Browse files
committed
Image Rounding Error with smooth scaling #62
This commit contributes to fixing the logic for scaling ImageData with Smooth scaling strategy. The logic of DPIUtil::autoScaleImageData is replicated in the Image class and modified to have properly defined current and target zoom. The previous implementation scales the bounds of image up and down several times which can lead to rounding error in case of scaling factor being a fractional value. With this implementation, the obtained imageData has no rounding error after ruling out those scale ups and downs and hence improves the menu item icons. contributes to #62 and #127
1 parent 1bc7548 commit af9dd04

File tree

4 files changed

+78
-15
lines changed

4 files changed

+78
-15
lines changed

bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java

+1
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,7 @@ public class OS extends C {
12461246
public static final int SM_CYFOCUSBORDER = 84;
12471247
public static final int SM_CYHSCROLL = 0x3;
12481248
public static final int SM_CYMENU = 0xf;
1249+
public static final int SM_CYMENUCHECK = 72;
12491250
public static final int SM_CXMINTRACK = 34;
12501251
public static final int SM_CYMINTRACK = 35;
12511252
public static final int SM_CXMAXTRACK = 59;

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ private static ImageData autoScaleImageData (Device device, final ImageData imag
292292
int height = imageData.height;
293293
int scaledWidth = Math.round (width * scaleFactor);
294294
int scaledHeight = Math.round (height * scaleFactor);
295-
boolean useSmoothScaling = autoScaleMethod == AutoScaleMethod.SMOOTH && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK;
295+
boolean useSmoothScaling = isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK;
296296
if (useSmoothScaling) {
297297
Image original = new Image (device, (ImageDataProvider) zoom -> imageData);
298298
/* Create a 24 bit image data with alpha channel */
@@ -316,6 +316,10 @@ private static ImageData autoScaleImageData (Device device, final ImageData imag
316316
}
317317
}
318318

319+
public static boolean isSmoothScalingEnabled() {
320+
return autoScaleMethod == AutoScaleMethod.SMOOTH;
321+
}
322+
319323
/**
320324
* Returns a new rectangle as per the scaleFactor.
321325
*/
@@ -631,7 +635,19 @@ public static boolean useCairoAutoScale() {
631635
return useCairoAutoScale;
632636
}
633637

638+
public static int getZoomForMenuItemImage(int nativeDeviceZoom) {
639+
String autoScaleValueForMenuItemImage = DPIUtil.autoScaleValue;
640+
if(autoScaleValueForMenuItemImage.equals("quarter") || autoScaleValueForMenuItemImage.equals("exact")) {
641+
autoScaleValueForMenuItemImage = "half";
642+
}
643+
return getZoomForAutoscaleProperty(nativeDeviceZoom, autoScaleValueForMenuItemImage);
644+
}
645+
634646
public static int getZoomForAutoscaleProperty (int nativeDeviceZoom) {
647+
return getZoomForAutoscaleProperty(nativeDeviceZoom, autoScaleValue);
648+
}
649+
650+
private static int getZoomForAutoscaleProperty (int nativeDeviceZoom, String autoScaleValue) {
635651
int zoom = 0;
636652
if (autoScaleValue != null) {
637653
if ("false".equalsIgnoreCase (autoScaleValue)) {

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

+49-12
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.eclipse.swt.internal.DPIUtil.*;
2323
import org.eclipse.swt.internal.gdip.*;
2424
import org.eclipse.swt.internal.win32.*;
25+
import org.eclipse.swt.widgets.*;
2526

2627
/**
2728
* Instances of this class are graphics which have been prepared
@@ -363,7 +364,7 @@ public Image(Device device, ImageData data) {
363364
super(device);
364365
if (data == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
365366
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
366-
data = DPIUtil.autoScaleUp(device, new ElementAtZoom<>(data, 100));
367+
data = scaleImageData(device, 100, getZoom(), data);
367368
init(data, getZoom());
368369
init();
369370
this.device.registerResourceWithZoomSupport(this);
@@ -407,8 +408,8 @@ public Image(Device device, ImageData source, ImageData mask) {
407408
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
408409
}
409410
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
410-
source = DPIUtil.autoScaleUp(device, source);
411-
mask = DPIUtil.autoScaleUp(device, mask);
411+
source = scaleImageData(device, 100, getZoom(), source);
412+
mask = scaleImageData(device, 100, getZoom(), mask);
412413
mask = ImageData.convertMask(mask);
413414
init(this.device, this, source, mask, getZoom());
414415
init();
@@ -471,7 +472,7 @@ public Image(Device device, ImageData source, ImageData mask) {
471472
public Image (Device device, InputStream stream) {
472473
super(device);
473474
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
474-
ImageData data = DPIUtil.autoScaleUp(device, new ElementAtZoom<>(new ImageData (stream), 100));
475+
ImageData data = scaleImageData(device, 100, getZoom(), new ImageData (stream));
475476
init(data, getZoom());
476477
init();
477478
this.device.registerResourceWithZoomSupport(this);
@@ -513,7 +514,7 @@ public Image (Device device, String filename) {
513514
super(device);
514515
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
515516
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
516-
ImageData data = DPIUtil.autoScaleUp(device, new ElementAtZoom<>(new ImageData (filename), 100));
517+
ImageData data = scaleImageData(device, 100, getZoom(), new ImageData (filename));
517518
init(data, getZoom());
518519
init();
519520
this.device.registerResourceWithZoomSupport(this);
@@ -559,7 +560,7 @@ public Image(Device device, ImageFileNameProvider imageFileNameProvider) {
559560
init(new ImageData (fileName.element()), getZoom());
560561
}
561562
} else {
562-
ImageData resizedData = DPIUtil.autoScaleImageData (device, new ImageData (fileName.element()), fileName.zoom());
563+
ImageData resizedData = scaleImageData(device, fileName.zoom(), getZoom(), new ImageData (fileName.element()));
563564
init(resizedData, getZoom());
564565
}
565566
init();
@@ -600,7 +601,7 @@ public Image(Device device, ImageDataProvider imageDataProvider) {
600601
this.imageProvider = new ImageDataProviderWrapper(imageDataProvider);
601602
initialNativeZoom = DPIUtil.getNativeDeviceZoom();
602603
ElementAtZoom<ImageData> data = DPIUtil.validateAndGetImageDataAtZoom(imageDataProvider, getZoom());
603-
ImageData resizedData = DPIUtil.scaleImageData(device, data.element(), getZoom(), data.zoom());
604+
ImageData resizedData = scaleImageData(device, data.zoom(), getZoom(), data.element());
604605
init (resizedData, getZoom());
605606
init();
606607
this.device.registerResourceWithZoomSupport(this);
@@ -1420,7 +1421,7 @@ public ImageData getImageData (int zoom) {
14201421
}
14211422
TreeSet<Integer> availableZooms = new TreeSet<>(zoomLevelToImageHandle.keySet());
14221423
int closestZoom = Optional.ofNullable(availableZooms.higher(zoom)).orElse(availableZooms.lower(zoom));
1423-
return DPIUtil.scaleImageData (device, getImageMetadata(closestZoom).getImageData(), zoom, closestZoom);
1424+
return scaleImageData(device, closestZoom, zoom, getImageMetadata(closestZoom).getImageData());
14241425
}
14251426

14261427
/**
@@ -2048,6 +2049,41 @@ private void setBackground(Color color, long handle) {
20482049
device.internal_dispose_GC(hDC, null);
20492050
}
20502051

2052+
private ImageData scaleImageData(Device device, int currentZoom, int targetZoom, ImageData imageData) {
2053+
if (imageData == null || targetZoom == currentZoom || (device != null && !device.isAutoScalable())) return imageData;
2054+
float scaleFactor = (float) targetZoom / (float) currentZoom;
2055+
int width = imageData.width;
2056+
int height = imageData.height;
2057+
int scaledWidth = Math.round (width * scaleFactor);
2058+
int scaledHeight = Math.round (height * scaleFactor);
2059+
boolean useSmoothScaling = DPIUtil.isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK;
2060+
if (useSmoothScaling) {
2061+
return scaleToUsingSmoothScaling(scaledWidth, scaledHeight, imageData);
2062+
} else {
2063+
return imageData.scaledTo (scaledWidth, scaledHeight);
2064+
}
2065+
}
2066+
2067+
private ImageData scaleToUsingSmoothScaling(int width, int height, ImageData imageData) {
2068+
Image original = new Image (Display.getCurrent(), (ImageDataProvider) zoom -> imageData);
2069+
/* Create a 24 bit image data with alpha channel */
2070+
final ImageData resultData = new ImageData (width, height, 24, new PaletteData (0xFF, 0xFF00, 0xFF0000));
2071+
resultData.alphaData = new byte [width * height];
2072+
Image resultImage = new Image (Display.getCurrent(), (ImageDataProvider) zoom -> resultData);
2073+
GC gc = new GC (resultImage);
2074+
gc.setAntialias (SWT.ON);
2075+
gc.drawImage (original, 0, 0, imageData.width, imageData.height,
2076+
/* E.g. destWidth here is effectively DPIUtil.autoScaleDown (scaledWidth), but avoiding rounding errors.
2077+
* Nevertheless, we still have some rounding errors due to the point-based API GC#drawImage(..).
2078+
*/
2079+
0, 0, width, height, false);
2080+
gc.dispose ();
2081+
original.dispose ();
2082+
ImageData result = resultImage.getImageData (resultImage.getZoom());
2083+
resultImage.dispose ();
2084+
return result;
2085+
}
2086+
20512087
private int getZoom() {
20522088
return DPIUtil.getZoomForAutoscaleProperty(initialNativeZoom);
20532089
}
@@ -2138,7 +2174,8 @@ protected Rectangle getBounds(int zoom) {
21382174
@Override
21392175
ImageData getImageData(int zoom) {
21402176
ElementAtZoom<String> fileName = DPIUtil.validateAndGetImagePathAtZoom (provider, zoom);
2141-
return DPIUtil.scaleImageData (device, new ImageData (fileName.element()), zoom, fileName.zoom());
2177+
ImageData imageData = new ImageData (fileName.element());
2178+
return scaleImageData(device, fileName.zoom(), zoom, imageData);
21422179
}
21432180

21442181
@Override
@@ -2151,7 +2188,7 @@ ImageHandle getImageMetadata(int zoom) {
21512188
if (imageMetadata == null) init(imageData, zoom);
21522189
init();
21532190
} else {
2154-
ImageData resizedData = DPIUtil.scaleImageData(device, imageData, zoom, imageCandidate.zoom());
2191+
ImageData resizedData = scaleImageData(device, imageCandidate.zoom(), zoom, imageData);
21552192
ImageData newData = adaptImageDataIfDisabledOrGray(resizedData);
21562193
init(newData, zoom);
21572194
}
@@ -2201,13 +2238,13 @@ protected Rectangle getBounds(int zoom) {
22012238
@Override
22022239
ImageData getImageData(int zoom) {
22032240
ElementAtZoom<ImageData> data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom);
2204-
return DPIUtil.scaleImageData (device, data.element(), zoom, data.zoom());
2241+
return scaleImageData(device, data.zoom(), zoom, data.element());
22052242
}
22062243

22072244
@Override
22082245
ImageHandle getImageMetadata(int zoom) {
22092246
ElementAtZoom<ImageData> imageCandidate = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom);
2210-
ImageData resizedData = DPIUtil.scaleImageData (device, imageCandidate.element(), zoom, imageCandidate.zoom());
2247+
ImageData resizedData = scaleImageData(device, imageCandidate.zoom(), zoom, imageCandidate.element());
22112248
ImageData newData = adaptImageDataIfDisabledOrGray(resizedData);
22122249
init(newData, zoom);
22132250
init();

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/MenuItem.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -781,8 +781,7 @@ public void setImage (Image image) {
781781
info.hbmpItem = OS.HBMMENU_CALLBACK;
782782
} else {
783783
if (OS.IsAppThemed ()) {
784-
if (hBitmap != 0) OS.DeleteObject (hBitmap);
785-
info.hbmpItem = hBitmap = image != null ? Display.create32bitDIB (image, getZoom()) : 0;
784+
info.hbmpItem = hBitmap = getMenuItemIconBitmapHandle(image);
786785
} else {
787786
info.hbmpItem = image != null ? OS.HBMMENU_CALLBACK : 0;
788787
}
@@ -792,6 +791,16 @@ public void setImage (Image image) {
792791
parent.redraw ();
793792
}
794793

794+
private long getMenuItemIconBitmapHandle(Image image) {
795+
if(image == null) {
796+
return 0;
797+
}
798+
if (hBitmap != 0) OS.DeleteObject (hBitmap);
799+
int desiredSize = getSystemMetrics(OS.SM_CYMENUCHECK);
800+
int zoom = (int) (((double) desiredSize / image.getBounds().height) * 100);
801+
return Display.create32bitDIB (image, zoom);
802+
}
803+
795804
/**
796805
* Sets the receiver's pull down menu to the argument.
797806
* Only <code>CASCADE</code> menu items can have a

0 commit comments

Comments
 (0)