org.eclipse.swt.internal.DPIUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.swt.gtk.linux.ppc64le Show documentation
Show all versions of org.eclipse.swt.gtk.linux.ppc64le Show documentation
Standard Widget Toolkit for GTK on ppc64le
The newest version!
/*******************************************************************************
* 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 - 2024 Weber Informatics LLC | Privacy Policy