![JAR search and dependency download from the Maven repository](/logo.png)
org.eclipse.swt.internal.DPIUtil Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2022 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Daniel Kruegler - #420 - [High DPI] "swt.autoScale" should add new "half" option
* Yatta Solutions - #131 - Additional methods to specify target zoom directly
*******************************************************************************/
package org.eclipse.swt.internal;
import java.util.function.*;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
/**
* This class hold common constants and utility functions w.r.t. to SWT high DPI
* functionality.
*
* The {@code autoScaleUp(..)} methods convert from API coordinates (in
* SWT points) to internal high DPI coordinates (in pixels) that interface with
* native widgets.
*
*
* The {@code autoScaleDown(..)} convert from high DPI pixels to API coordinates
* (in SWT points).
*
*
* @since 3.105
*/
public class DPIUtil {
private static final int DPI_ZOOM_100 = 96;
private static int deviceZoom = 100;
private static int nativeDeviceZoom = 100;
private static enum AutoScaleMethod { AUTO, NEAREST, SMOOTH }
private static AutoScaleMethod autoScaleMethodSetting = AutoScaleMethod.AUTO;
private static AutoScaleMethod autoScaleMethod = AutoScaleMethod.NEAREST;
private static boolean autoScaleOnRuntime = false;
private static String autoScaleValue;
private static boolean useCairoAutoScale = false;
/**
* System property that controls the autoScale functionality.
*
* - false: deviceZoom is set to 100%
* - integer: deviceZoom depends on the current display resolution,
* but only uses integer multiples of 100%. The detected native zoom is
* generally rounded down (e.g. at 150%, will use 100%), unless close to
* the next integer multiple (currently at 175%, will use 200%).
* - integer200: like integer, but the maximal zoom level is 200%.
* - half: deviceZoom depends on the current display resolution,
* but only uses integer multiples of 50%. The detected native zoom is
* rounded to the closest permissible value, with tie-breaker towards even.
* - quarter: deviceZoom depends on the current display resolution,
* but only uses integer multiples of 25%. The detected native zoom is
* rounded to the closest permissible value.
* - exact: deviceZoom uses the native zoom (with 1% as minimal
* step).
* - <value>: deviceZoom uses the given integer value in
* percent as zoom level.
*
* The current default is "integer200".
*/
private static final String SWT_AUTOSCALE = "swt.autoScale";
/**
* System property that controls the method for scaling images:
*
* - "nearest": nearest-neighbor interpolation, may look jagged
* - "smooth": smooth edges, may look blurry
*
* The current default is to use "nearest", except on
* GTK when the deviceZoom is not an integer multiple of 100%.
* The smooth strategy currently doesn't work on Win32 and Cocoa, see
* bug 493455.
*/
private static final String SWT_AUTOSCALE_METHOD = "swt.autoScale.method";
/**
* System property to enable to scale the application on runtime
* when a DPI change is detected.
*
* - "true": the application is scaled on DPI changes
* - "false": the application will remain in its initial scaling
*
* Important: This flag is only parsed and used on Win32. Setting it to
* true on GTK or cocoa will be ignored.
*/
private static final String SWT_AUTOSCALE_UPDATE_ON_RUNTIME = "swt.autoScale.updateOnRuntime";
static {
autoScaleValue = System.getProperty (SWT_AUTOSCALE);
String value = System.getProperty (SWT_AUTOSCALE_METHOD);
if (value != null) {
if (AutoScaleMethod.NEAREST.name().equalsIgnoreCase(value)) {
autoScaleMethod = autoScaleMethodSetting = AutoScaleMethod.NEAREST;
} else if (AutoScaleMethod.SMOOTH.name().equalsIgnoreCase(value)) {
autoScaleMethod = autoScaleMethodSetting = AutoScaleMethod.SMOOTH;
}
}
String updateOnRuntimeValue = System.getProperty (SWT_AUTOSCALE_UPDATE_ON_RUNTIME);
autoScaleOnRuntime = Boolean.parseBoolean(updateOnRuntimeValue);
}
/**
* Auto-scale down ImageData
*/
public static ImageData autoScaleDown (Device device, final ImageData imageData) {
if (deviceZoom == 100 || imageData == null || (device != null && !device.isAutoScalable())) return imageData;
float scaleFactor = 1.0f / getScalingFactor (deviceZoom);
return autoScaleImageData(device, imageData, scaleFactor);
}
public static int[] autoScaleDown(int[] pointArray) {
if (deviceZoom == 100 || pointArray == null) return pointArray;
float scaleFactor = getScalingFactor (deviceZoom);
int [] returnArray = new int[pointArray.length];
for (int i = 0; i < pointArray.length; i++) {
returnArray [i] = Math.round (pointArray [i] / scaleFactor);
}
return returnArray;
}
public static int[] autoScaleDown(Drawable drawable, int[] pointArray) {
if (drawable != null && !drawable.isAutoScalable ()) return pointArray;
return autoScaleDown (pointArray);
}
/**
* Auto-scale down float array dimensions.
*/
public static float[] autoScaleDown(float size[]) {
return scaleDown(size, deviceZoom);
}
public static float[] scaleDown(float size[], int zoom) {
if (zoom == 100 || size == null) return size;
float scaleFactor = getScalingFactor (zoom);
float scaledSize[] = new float[size.length];
for (int i = 0; i < scaledSize.length; i++) {
scaledSize[i] = size[i] / scaleFactor;
}
return scaledSize;
}
/**
* Auto-scale down float array dimensions if enabled for Drawable class.
*/
public static float[] autoScaleDown(Drawable drawable, float size[]) {
return scaleDown(drawable, size, deviceZoom);
}
public static float[] scaleDown(Drawable drawable, float size[], int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return size;
return scaleDown(size, zoom);
}
/**
* Auto-scale down int dimensions.
*/
public static int autoScaleDown(int size) {
return scaleDown(size, deviceZoom);
}
public static int scaleDown(int size, int zoom) {
if (zoom == 100 || size == SWT.DEFAULT) return size;
float scaleFactor = getScalingFactor (zoom);
return Math.round (size / scaleFactor);
}
/**
* Auto-scale down int dimensions if enabled for Drawable class.
*/
public static int autoScaleDown(Drawable drawable, int size) {
return scaleDown(drawable, size, deviceZoom);
}
public static int scaleDown(Drawable drawable, int size, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return size;
return scaleDown (size, zoom);
}
/**
* Auto-scale down float dimensions.
*/
public static float autoScaleDown(float size) {
return scaleDown(size, deviceZoom);
}
public static float scaleDown(float size, int zoom) {
if (zoom == 100 || size == SWT.DEFAULT) return size;
float scaleFactor = getScalingFactor (zoom);
return (size / scaleFactor);
}
/**
* Auto-scale down float dimensions if enabled for Drawable class.
*/
public static float autoScaleDown(Drawable drawable, float size) {
return scaleDown (drawable, size, deviceZoom);
}
public static float scaleDown(Drawable drawable, float size, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return size;
return scaleDown (size, zoom);
}
/**
* Returns a new scaled down Point.
*/
public static Point autoScaleDown(Point point) {
return scaleDown(point, deviceZoom);
}
public static Point scaleDown(Point point, int zoom) {
if (zoom == 100 || point == null) return point;
float scaleFactor = getScalingFactor(zoom);
Point scaledPoint = new Point (0,0);
scaledPoint.x = Math.round (point.x / scaleFactor);
scaledPoint.y = Math.round (point.y / scaleFactor);
return scaledPoint;
}
/**
* Returns a new scaled down Point if enabled for Drawable class.
*/
public static Point autoScaleDown(Drawable drawable, Point point) {
return scaleDown(drawable, point, deviceZoom);
}
public static Point scaleDown(Drawable drawable, Point point, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return point;
return scaleDown (point, zoom);
}
/**
* Returns a new scaled down Rectangle.
*/
public static Rectangle autoScaleDown(Rectangle rect) {
return scaleDown(rect, deviceZoom);
}
public static Rectangle scaleDown(Rectangle rect, int zoom) {
if (zoom == 100 || rect == null) return rect;
Rectangle scaledRect = new Rectangle (0,0,0,0);
Point scaledTopLeft = scaleDown(new Point (rect.x, rect.y), zoom);
Point scaledBottomRight = scaleDown(new Point (rect.x + rect.width, rect.y + rect.height), zoom);
scaledRect.x = scaledTopLeft.x;
scaledRect.y = scaledTopLeft.y;
scaledRect.width = scaledBottomRight.x - scaledTopLeft.x;
scaledRect.height = scaledBottomRight.y - scaledTopLeft.y;
return scaledRect;
}
/**
* Returns a new scaled down Rectangle if enabled for Drawable class.
*/
public static Rectangle autoScaleDown(Drawable drawable, Rectangle rect) {
if (drawable != null && !drawable.isAutoScalable()) return rect;
return scaleDown(rect, deviceZoom);
}
public static Rectangle scaleDown(Drawable drawable, Rectangle rect, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return rect;
return scaleDown (rect, zoom);
}
/**
* Auto-scale image with ImageData
*/
public static ImageData scaleImageData (Device device, final ImageData imageData, int targetZoom, int currentZoom) {
if (imageData == null || targetZoom == currentZoom || (device != null && !device.isAutoScalable())) return imageData;
float scaleFactor = (float) targetZoom / (float) currentZoom;
return autoScaleImageData(device, imageData, scaleFactor);
}
public static ImageData scaleImageData (Device device, final ElementAtZoom elementAtZoom, int targetZoom) {
return scaleImageData(device, elementAtZoom.element(), targetZoom, elementAtZoom.zoom());
}
private static ImageData autoScaleImageData (Device device, final ImageData imageData, float scaleFactor) {
// Guards are already implemented in callers: if (deviceZoom == 100 || imageData == null || scaleFactor == 1.0f) return imageData;
int width = imageData.width;
int height = imageData.height;
int scaledWidth = Math.round (width * scaleFactor);
int scaledHeight = Math.round (height * scaleFactor);
return switch (autoScaleMethod) {
case SMOOTH -> {
Image original = new Image (device, (ImageDataProvider) zoom -> imageData);
/* Create a 24 bit image data with alpha channel */
final ImageData resultData = new ImageData (scaledWidth, scaledHeight, 24, new PaletteData (0xFF, 0xFF00, 0xFF0000));
resultData.alphaData = new byte [scaledWidth * scaledHeight];
Image resultImage = new Image (device, (ImageDataProvider) zoom -> resultData);
GC gc = new GC (resultImage);
gc.setAntialias (SWT.ON);
gc.drawImage (original, 0, 0, autoScaleDown (width), autoScaleDown (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, Math.round (autoScaleDown (width * scaleFactor)), Math.round (autoScaleDown (height * scaleFactor)));
gc.dispose ();
original.dispose ();
ImageData result = resultImage.getImageData (getDeviceZoom ());
resultImage.dispose ();
yield result;
}
default -> imageData.scaledTo (scaledWidth, scaledHeight);
};
}
/**
* Returns a new rectangle as per the scaleFactor.
*/
public static Rectangle scaleBounds (Rectangle rect, int targetZoom, int currentZoom) {
if (rect == null || targetZoom == currentZoom) return rect;
float scaleFactor = ((float)targetZoom) / (float)currentZoom;
Rectangle returnRect = new Rectangle (0,0,0,0);
returnRect.x = Math.round (rect.x * scaleFactor);
returnRect.y = Math.round (rect.y * scaleFactor);
returnRect.width = Math.round (rect.width * scaleFactor);
returnRect.height = Math.round (rect.height * scaleFactor);
return returnRect;
}
/**
* Auto-scale ImageData to device zoom that are at given zoom factor.
*/
public static ImageData autoScaleImageData (Device device, final ImageData imageData, int imageDataZoomFactor) {
if (deviceZoom == imageDataZoomFactor || imageData == null || (device != null && !device.isAutoScalable())) return imageData;
float scaleFactor = (float) deviceZoom / imageDataZoomFactor;
return autoScaleImageData(device, imageData, scaleFactor);
}
/**
* Auto-scale up ImageData to device zoom that is at 100%.
*/
public static ImageData autoScaleUp (Device device, final ImageData imageData) {
return autoScaleImageData(device, imageData, 100);
}
public static ImageData autoScaleUp (Device device, final ElementAtZoom elementAtZoom) {
return autoScaleImageData(device, elementAtZoom.element(), elementAtZoom.zoom());
}
public static int[] autoScaleUp(int[] pointArray) {
return scaleUp(pointArray, deviceZoom);
}
public static int[] scaleUp(int[] pointArray, int zoom) {
if (zoom == 100 || pointArray == null) return pointArray;
float scaleFactor = getScalingFactor(zoom);
int[] returnArray = new int[pointArray.length];
for (int i = 0; i < pointArray.length; i++) {
returnArray [i] = Math.round (pointArray [i] * scaleFactor);
}
return returnArray;
}
public static int[] autoScaleUp(Drawable drawable, int[] pointArray) {
return scaleUp(drawable, pointArray, deviceZoom);
}
public static int[] scaleUp(Drawable drawable, int[] pointArray, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return pointArray;
return scaleUp (pointArray, zoom);
}
/**
* Auto-scale up int dimensions.
*/
public static int autoScaleUp(int size) {
return scaleUp(size, deviceZoom);
}
/**
* Auto-scale up int dimensions to match the given zoom level
*/
public static int scaleUp(int size, int zoom) {
if (zoom == 100 || size == SWT.DEFAULT) return size;
float scaleFactor = getScalingFactor(zoom);
return Math.round (size * scaleFactor);
}
/**
* Auto-scale up int dimensions if enabled for Drawable class.
*/
public static int autoScaleUp(Drawable drawable, int size) {
return scaleUp(drawable, size, deviceZoom);
}
public static int scaleUp(Drawable drawable, int size, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return size;
return scaleUp (size, zoom);
}
public static float autoScaleUp(float size) {
return scaleUp(size, deviceZoom);
}
public static float scaleUp(float size, int zoom) {
if (zoom == 100 || size == SWT.DEFAULT) return size;
float scaleFactor = getScalingFactor(zoom);
return (size * scaleFactor);
}
public static float autoScaleUp(Drawable drawable, float size) {
return scaleUp(drawable, size, deviceZoom);
}
public static float scaleUp(Drawable drawable, float size, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return size;
return scaleUp (size, zoom);
}
/**
* Returns a new scaled up Point.
*/
public static Point autoScaleUp(Point point) {
return scaleUp(point, deviceZoom);
}
public static Point scaleUp(Point point, int zoom) {
if (zoom == 100 || point == null) return point;
float scaleFactor = getScalingFactor(zoom);
Point scaledPoint = new Point(0,0);
scaledPoint.x = Math.round (point.x * scaleFactor);
scaledPoint.y = Math.round (point.y * scaleFactor);
return scaledPoint;
}
/**
* Returns a new scaled up Point if enabled for Drawable class.
*/
public static Point autoScaleUp(Drawable drawable, Point point) {
return scaleUp (drawable, point, deviceZoom);
}
public static Point scaleUp(Drawable drawable, Point point, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return point;
return scaleUp (point, zoom);
}
/**
* Returns a new scaled up Rectangle.
*/
public static Rectangle autoScaleUp(Rectangle rect) {
return scaleUp(rect, deviceZoom);
}
public static Rectangle scaleUp(Rectangle rect, int zoom) {
if (zoom == 100 || rect == null) return rect;
Rectangle scaledRect = new Rectangle(0,0,0,0);
Point scaledTopLeft = scaleUp (new Point(rect.x, rect.y), zoom);
Point scaledBottomRight = scaleUp (new Point(rect.x + rect.width, rect.y + rect.height), zoom);
scaledRect.x = scaledTopLeft.x;
scaledRect.y = scaledTopLeft.y;
scaledRect.width = scaledBottomRight.x - scaledTopLeft.x;
scaledRect.height = scaledBottomRight.y - scaledTopLeft.y;
return scaledRect;
}
/**
* Returns a new scaled up Rectangle if enabled for Drawable class.
*/
public static Rectangle autoScaleUp(Drawable drawable, Rectangle rect) {
return scaleUp(drawable, rect, deviceZoom);
}
public static Rectangle scaleUp(Drawable drawable, Rectangle rect, int zoom) {
if (drawable != null && !drawable.isAutoScalable()) return rect;
return scaleUp (rect, zoom);
}
/**
* Returns scaling factor from the given device zoom
* @return float scaling factor
*/
private static float getScalingFactor(int zoom) {
if (useCairoAutoScale) {
return 1;
}
if (zoom <= 0) {
zoom = deviceZoom;
}
return zoom / 100f;
}
/**
* Compute the zoom value based on the DPI value.
*
* @return zoom
*/
public static int mapDPIToZoom (int dpi) {
double zoom = (double) dpi * 100 / DPI_ZOOM_100;
int roundedZoom = (int) Math.round (zoom);
return roundedZoom;
}
/**
* Compute the DPI value value based on the zoom.
*
* @return DPI
*/
public static int mapZoomToDPI (int zoom) {
double dpi = (double) zoom / 100 * DPI_ZOOM_100;
int roundedDpi = (int) Math.round (dpi);
return roundedDpi;
}
/**
* Represents an element, such as some image data, at a specific zoom level.
*
* @param type of the element to be presented, e.g., {@link ImageData}
*/
public record ElementAtZoom(T element, int zoom) {
}
/**
* Gets ImageData that are appropriate for the specified zoom level together
* with the zoom level at which the image data are. If there is an image at the
* specified zoom level, it is returned. Otherwise the next larger image at 150%
* and 200% is returned, if existing. If none of these is found, the 100% image
* is returned as a fallback. If provider or fallback image is not available, an
* error is thrown.
*/
public static ElementAtZoom validateAndGetImageDataAtZoom(ImageDataProvider provider, int zoom) {
if (provider == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
ElementAtZoom imageDataAtZoom = getElementAtZoom(z -> provider.getImageData(z), zoom);
if (imageDataAtZoom == null) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null,
": ImageDataProvider [" + provider + "] returns null ImageData at 100% zoom.");
}
return imageDataAtZoom;
}
/**
* Gets the image file path that are appropriate for the specified zoom level
* together with the zoom level at which the image data are. If there is an
* image at the specified zoom level, it is returned. Otherwise the next larger
* image at 150% and 200% is returned, if existing. If none of these is found,
* the 100% image is returned as a fallback. If provider or fallback image is
* not available, an error is thrown.
*/
public static ElementAtZoom validateAndGetImagePathAtZoom(ImageFileNameProvider provider, int zoom) {
if (provider == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
ElementAtZoom imagePathAtZoom = getElementAtZoom(z -> provider.getImagePath(z), zoom);
if (imagePathAtZoom == null) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null,
": ImageFileNameProvider [" + provider + "] returns null filename at 100% zoom.");
}
return imagePathAtZoom;
}
private static ElementAtZoom getElementAtZoom(Function elementForZoomProvider, int zoom) {
T dataAtOriginalZoom = elementForZoomProvider.apply(zoom);
if (dataAtOriginalZoom != null) {
return new ElementAtZoom<>(dataAtOriginalZoom, zoom);
}
if (zoom > 100 && zoom <= 150) {
T dataAt150Percent = elementForZoomProvider.apply(150);
if (dataAt150Percent != null) {
return new ElementAtZoom<>(dataAt150Percent, 150);
}
}
if (zoom > 100) {
T dataAt200Percent = elementForZoomProvider.apply(200);
if (dataAt200Percent != null) {
return new ElementAtZoom<>(dataAt200Percent, 200);
}
}
if (zoom != 100) {
T dataAt100Percent = elementForZoomProvider.apply(100);
if (dataAt100Percent != null) {
return new ElementAtZoom<>(dataAt100Percent, 100);
}
}
return null;
}
public static int getNativeDeviceZoom() {
return nativeDeviceZoom;
}
public static int getDeviceZoom() {
return deviceZoom;
}
public static void setDeviceZoom (int nativeDeviceZoom) {
DPIUtil.nativeDeviceZoom = nativeDeviceZoom;
int deviceZoom = getZoomForAutoscaleProperty (nativeDeviceZoom);
DPIUtil.deviceZoom = deviceZoom;
System.setProperty("org.eclipse.swt.internal.deviceZoom", Integer.toString(deviceZoom));
if (deviceZoom != 100 && autoScaleMethodSetting == AutoScaleMethod.AUTO) {
if (deviceZoom / 100 * 100 == deviceZoom || !"gtk".equals(SWT.getPlatform())) {
autoScaleMethod = AutoScaleMethod.NEAREST;
} else {
autoScaleMethod = AutoScaleMethod.SMOOTH;
}
}
}
public static void setUseCairoAutoScale (boolean cairoAutoScale) {
useCairoAutoScale = cairoAutoScale;
}
public static boolean useCairoAutoScale() {
return useCairoAutoScale;
}
public static int getZoomForAutoscaleProperty (int nativeDeviceZoom) {
int zoom = 0;
if (autoScaleValue != null) {
if ("false".equalsIgnoreCase (autoScaleValue)) {
zoom = 100;
} else if ("half".equalsIgnoreCase (autoScaleValue)) {
// Math.round rounds 125->150 and 175->200,
// Math.rint rounds 125->100 and 175->200 matching
// "integer200"
zoom = (int) Math.rint(nativeDeviceZoom / 50d) * 50;
} else if ("quarter".equalsIgnoreCase (autoScaleValue)) {
zoom = Math.round(nativeDeviceZoom / 25f) * 25;
} else if ("exact".equalsIgnoreCase (autoScaleValue)) {
zoom = nativeDeviceZoom;
} else {
try {
int zoomValue = Integer.parseInt (autoScaleValue);
zoom = Math.max (Math.min (zoomValue, 1600), 25);
} catch (NumberFormatException e) {
// unsupported value, use default
}
}
}
if (zoom == 0) { // || "integer".equalsIgnoreCase (value) || "integer200".equalsIgnoreCase (value)
zoom = Math.max ((nativeDeviceZoom + 25) / 100 * 100, 100);
}
return zoom;
}
public static boolean isAutoScaleOnRuntimeActive() {
return autoScaleOnRuntime;
}
/**
* AutoScale ImageDataProvider.
*/
public static final class AutoScaleImageDataProvider implements ImageDataProvider {
Device device;
ImageData imageData;
int currentZoom;
public AutoScaleImageDataProvider(Device device, ImageData data, int zoom){
this.device = device;
this.imageData = data;
this.currentZoom = zoom;
}
@Override
public ImageData getImageData(int zoom) {
return DPIUtil.scaleImageData(device, imageData, zoom, currentZoom);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy