From 6f78753b3df426b8b1abda6cf187c720e2d4f6bf Mon Sep 17 00:00:00 2001 From: Amartya Parijat Date: Tue, 18 Mar 2025 14:21:04 +0100 Subject: [PATCH] Smooth Scaling Rounding error fix for win32 #62 This commit contributes to fixing the implementation of Smooth scaling of the ImageData to get rid of the rounding errors because of multiple scale ups and downs with fractional scale factor. The commit replicates and modifies the DPIUtil::autoScaleImageData method implementation in Image class to adapt the same. contributes to https://github.com/eclipse-platform/eclipse.platform.swt/issues/62 and https://github.com/eclipse-platform/eclipse.platform.swt/issues/127 --- .../org/eclipse/swt/internal/DPIUtil.java | 10 +++- .../win32/org/eclipse/swt/graphics/Image.java | 54 +++++++++++++++---- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java index 40dd39211c6..3cc772fb956 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java @@ -292,7 +292,7 @@ private static ImageData autoScaleImageData (Device device, final ImageData imag int height = imageData.height; int scaledWidth = Math.round (width * scaleFactor); int scaledHeight = Math.round (height * scaleFactor); - boolean useSmoothScaling = autoScaleMethod == AutoScaleMethod.SMOOTH && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK; + boolean useSmoothScaling = isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK; if (useSmoothScaling) { Image original = new Image (device, (ImageDataProvider) zoom -> imageData); /* Create a 24 bit image data with alpha channel */ @@ -316,6 +316,10 @@ private static ImageData autoScaleImageData (Device device, final ImageData imag } } +public static boolean isSmoothScalingEnabled() { + return autoScaleMethod == AutoScaleMethod.SMOOTH; +} + /** * Returns a new rectangle as per the scaleFactor. */ @@ -628,6 +632,10 @@ public static boolean useCairoAutoScale() { } public static int getZoomForAutoscaleProperty (int nativeDeviceZoom) { + return getZoomForAutoscaleProperty(nativeDeviceZoom, autoScaleValue); +} + +private static int getZoomForAutoscaleProperty (int nativeDeviceZoom, String autoScaleValue) { int zoom = 0; if (autoScaleValue != null) { if ("false".equalsIgnoreCase (autoScaleValue)) { 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 34c4ea9bbe0..e4454867a34 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 @@ -361,7 +361,7 @@ public Image(Device device, ImageData data) { if (data == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); initialNativeZoom = DPIUtil.getNativeDeviceZoom(); int deviceZoom = getZoom(); - data = DPIUtil.scaleImageData(device, new ElementAtZoom<>(data, 100), deviceZoom); + data = scaleImageData(data, deviceZoom, 100); init(data, deviceZoom); init(); this.device.registerResourceWithZoomSupport(this); @@ -405,8 +405,8 @@ public Image(Device device, ImageData source, ImageData mask) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } initialNativeZoom = DPIUtil.getNativeDeviceZoom(); - source = DPIUtil.autoScaleUp(device, source); - mask = DPIUtil.autoScaleUp(device, mask); + source = scaleImageData(source, getZoom(), 100); + mask = scaleImageData(mask, getZoom(), 100); mask = ImageData.convertMask(mask); initIconHandle(this.device, source, mask, getZoom()); init(); @@ -470,7 +470,8 @@ public Image (Device device, InputStream stream) { super(device); initialNativeZoom = DPIUtil.getNativeDeviceZoom(); int deviceZoom = getZoom(); - ImageData data = DPIUtil.scaleImageData(device, ImageDataLoader.load(stream, FileFormat.DEFAULT_ZOOM, deviceZoom), deviceZoom); + ElementAtZoom imageCandidate = ImageDataLoader.load(stream, FileFormat.DEFAULT_ZOOM, deviceZoom); + ImageData data = scaleImageData(imageCandidate.element(), deviceZoom, imageCandidate.zoom()); init(data, deviceZoom); init(); this.device.registerResourceWithZoomSupport(this); @@ -513,7 +514,8 @@ public Image (Device device, String filename) { if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); initialNativeZoom = DPIUtil.getNativeDeviceZoom(); int deviceZoom = getZoom(); - ImageData data = DPIUtil.scaleImageData(device, ImageDataLoader.load(filename, FileFormat.DEFAULT_ZOOM, deviceZoom), deviceZoom); + ElementAtZoom imageCandidate = ImageDataLoader.load(filename, FileFormat.DEFAULT_ZOOM, deviceZoom); + ImageData data = scaleImageData(imageCandidate.element(), deviceZoom, imageCandidate.zoom()); init(data, deviceZoom); init(); this.device.registerResourceWithZoomSupport(this); @@ -1237,7 +1239,7 @@ private ImageData getScaledImageData (int zoom) { } TreeSet availableZooms = new TreeSet<>(zoomLevelToImageHandle.keySet()); int closestZoom = Optional.ofNullable(availableZooms.higher(zoom)).orElse(availableZooms.lower(zoom)); - return DPIUtil.scaleImageData (device, getImageMetadata(closestZoom).getImageData(), zoom, closestZoom); + return scaleImageData(getImageMetadata(closestZoom).getImageData(), zoom, closestZoom); } @@ -1850,6 +1852,40 @@ private void setBackground(Color color, long handle) { device.internal_dispose_GC(hDC, null); } +private ImageData scaleImageData(final ImageData imageData, int targetZoom, int currentZoom) { + if (imageData == null || targetZoom == currentZoom || (device != null && !device.isAutoScalable())) return imageData; + float scaleFactor = (float) targetZoom / (float) currentZoom; + int width = imageData.width; + int height = imageData.height; + int scaledWidth = Math.round (width * scaleFactor); + int scaledHeight = Math.round (height * scaleFactor); + boolean useSmoothScaling = DPIUtil.isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK; + if (useSmoothScaling) { + return scaleToUsingSmoothScaling(scaledWidth, scaledHeight, imageData); + } + return imageData.scaledTo (scaledWidth, scaledHeight); +} + +private ImageData scaleToUsingSmoothScaling(int width, int height, ImageData imageData) { + Image original = new Image (device, (ImageDataProvider) zoom -> imageData); + /* Create a 24 bit image data with alpha channel */ + final ImageData resultData = new ImageData (width, height, 24, new PaletteData (0xFF, 0xFF00, 0xFF0000)); + resultData.alphaData = new byte [width * height]; + Image resultImage = new Image (device, (ImageDataProvider) zoom -> resultData); + GC gc = new GC (resultImage); + gc.setAntialias (SWT.ON); + gc.drawImage (original, 0, 0, imageData.width, imageData.height, + /* E.g. destWidth here is effectively DPIUtil.autoScaleDown (scaledWidth), but avoiding rounding errors. + * Nevertheless, we still have some rounding errors due to the point-based API GC#drawImage(..). + */ + 0, 0, width, height, false); + gc.dispose (); + original.dispose (); + ImageData result = resultImage.getImageData (resultImage.getZoom()); + resultImage.dispose (); + return result; +} + private int getZoom() { return DPIUtil.getZoomForAutoscaleProperty(initialNativeZoom); } @@ -2059,7 +2095,7 @@ ImageData getImageData(int zoom) { private ImageData scaleIfNecessary(ElementAtZoom imageDataAtZoom, int zoom) { if (imageDataAtZoom.zoom() != zoom) { - return DPIUtil.scaleImageData(device, imageDataAtZoom, zoom); + return scaleImageData(imageDataAtZoom.element(), zoom, imageDataAtZoom.zoom()); } else { return imageDataAtZoom.element(); } @@ -2308,13 +2344,13 @@ protected Rectangle getBounds(int zoom) { @Override ImageData getImageData(int zoom) { ElementAtZoom data = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom); - return DPIUtil.scaleImageData (device, data.element(), zoom, data.zoom()); + return scaleImageData(data.element(), zoom, data.zoom()); } @Override ImageHandle getImageMetadata(int zoom) { ElementAtZoom imageCandidate = DPIUtil.validateAndGetImageDataAtZoom (provider, zoom); - ImageData resizedData = DPIUtil.scaleImageData (device, imageCandidate.element(), zoom, imageCandidate.zoom()); + ImageData resizedData = scaleImageData(imageCandidate.element(), zoom, imageCandidate.zoom()); ImageData newData = adaptImageDataIfDisabledOrGray(resizedData); init(newData, zoom); return zoomLevelToImageHandle.get(zoom);