From 82de54f5228436d14cf9ab8dda32e1845502e0f5 Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Wed, 2 Jul 2025 22:37:17 +0200 Subject: [PATCH] Improve HighDPI support for Swing designer The current implementation always takes a screenshot of the Swing components at 100% zoom, which is then up-scaled to match the display zoom. Doing so results in blurry and low-quality images. Instead, the screenshot is taken at the current zoom level and contained in an ImageDataProvider. When the image is painted this then allows us to use the correctly scaled image data. Note that artifical scaling is still done, if a different zoom level is requested. Closes https://github.com/eclipse-windowbuilder/windowbuilder/issues/506 --- .../html-src/whatsnew/v121.asciidoc | 21 ++++ .../html/whatsnew/images/1.21/Scaling_New.png | Bin 0 -> 19164 bytes .../html/whatsnew/images/1.21/Scaling_Old.png | Bin 0 -> 18622 bytes org.eclipse.wb.doc.user/toc.xml | 2 +- .../internal/swing/utils/SwingImageUtils.java | 107 +++++++++++++++--- .../swing/utils/SwingScreenshotMaker.java | 32 ++++-- 6 files changed, 133 insertions(+), 29 deletions(-) create mode 100644 org.eclipse.wb.doc.user/html-src/whatsnew/v121.asciidoc create mode 100644 org.eclipse.wb.doc.user/html/whatsnew/images/1.21/Scaling_New.png create mode 100644 org.eclipse.wb.doc.user/html/whatsnew/images/1.21/Scaling_Old.png diff --git a/org.eclipse.wb.doc.user/html-src/whatsnew/v121.asciidoc b/org.eclipse.wb.doc.user/html-src/whatsnew/v121.asciidoc new file mode 100644 index 000000000..3aba06e1e --- /dev/null +++ b/org.eclipse.wb.doc.user/html-src/whatsnew/v121.asciidoc @@ -0,0 +1,21 @@ +ifdef::env-github[] +:imagesdir: ../../html/whatsnew +endif::[] + += What's New - v1.21.0 + +== Swing + +- Improved HighDPI support for Swing components + +Previously, the preview for Swing components was taken at 100% zoom and then +artificially upscaled to the current display zoom, leading to blurry results in +the designer. Components are now directly captured with the correct zoom level. + +[cols="a,a"] +|=== +| image:images/1.21/Scaling_Old.png[Old scaling at 200% zoom] +| image:images/1.21/Scaling_New.png[New scaling at 200% zoom] +|=== + +What's new - xref:v120.adoc[*v1.20.0*] \ No newline at end of file diff --git a/org.eclipse.wb.doc.user/html/whatsnew/images/1.21/Scaling_New.png b/org.eclipse.wb.doc.user/html/whatsnew/images/1.21/Scaling_New.png new file mode 100644 index 0000000000000000000000000000000000000000..f5973620e38eb1156ca62dcdb2957f0c6ad170b9 GIT binary patch literal 19164 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

#PEEtVACG_^q%0|9ir1|?!&cODLg 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 literal 0 HcmV?d00001 diff --git a/org.eclipse.wb.doc.user/toc.xml b/org.eclipse.wb.doc.user/toc.xml index c0e41c367..ac4774888 100644 --- a/org.eclipse.wb.doc.user/toc.xml +++ b/org.eclipse.wb.doc.user/toc.xml @@ -2,7 +2,7 @@ - + diff --git a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/utils/SwingImageUtils.java b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/utils/SwingImageUtils.java index 471bb1839..183a790c6 100644 --- a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/utils/SwingImageUtils.java +++ b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/utils/SwingImageUtils.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011, 2024 Google, Inc. and others. + * Copyright (c) 2011, 2025 Google, Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -28,6 +28,8 @@ import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.ImageDataProvider; +import org.eclipse.swt.graphics.PaletteData; import org.apache.commons.lang3.StringUtils; @@ -36,6 +38,7 @@ import java.awt.Container; import java.awt.Dialog; import java.awt.EventQueue; +import java.awt.Graphics2D; import java.awt.Insets; import java.awt.Point; import java.awt.Window; @@ -44,6 +47,7 @@ import java.awt.image.ImageConsumer; import java.awt.image.ImageProducer; import java.util.ArrayList; +import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.WeakHashMap; @@ -72,7 +76,8 @@ public class SwingImageUtils { * @return the {@link ImageDescriptor} of given {@link Component}. */ public static ImageDescriptor createComponentShot(final Component component) throws Exception { - return SwingUtils.runObjectLaterAndWait(() -> convertImage_AWT_to_SWT(createComponentShotAWT(component))); + double zoom = getDisplayZoom(component); + return SwingUtils.runObjectLaterAndWait(() -> convertImage_AWT_to_SWT(createComponentShotAWT(component), zoom)); } /** @@ -82,13 +87,14 @@ public static ImageDescriptor createComponentShot(final Component component) thr static java.awt.Image createComponentShotAWT(final Component component) throws Exception { Assert.isNotNull(component); // prepare sizes - final int componentWidth = component.getWidth(); - final int componentHeight = component.getHeight(); + final double componentZoom = getDisplayZoom(component); + final int componentWidth = (int) (component.getWidth() * componentZoom); + final int componentHeight = (int) (component.getHeight() * componentZoom); final int imageWidth = Math.max(1, componentWidth); final int imageHeight = Math.max(1, componentHeight); // prepare empty image - final BufferedImage componentImage = - new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB); + final BufferedImage componentImage = component.getGraphicsConfiguration() // + .createCompatibleImage(imageWidth, imageHeight); // If actual size on component is zero, then we are done. if (componentWidth == 0 || componentHeight == 0) { return componentImage; @@ -101,7 +107,12 @@ static java.awt.Image createComponentShotAWT(final Component component) throws E // Linux only: it seems that printAll() should be invoked in AWT dispatch thread // to prevent deadlocks between main thread and AWT event queue. // See also SwingUtils.invokeLaterAndWait(). - runInDispatchThread(() -> component.printAll(componentImage.getGraphics())); + runInDispatchThread(() -> { + Graphics2D graphics = componentImage.createGraphics(); + graphics.scale(componentZoom, componentZoom); + component.printAll(graphics); + graphics.dispose(); + }); } finally { shotConfigurator.dispose(); } @@ -500,24 +511,23 @@ private static void fetchMenuVisualData_items(MenuVisualData menuData, Container // //////////////////////////////////////////////////////////////////////////// /** - * Converts AWT image into SWT one. Yours, C.O. ;-) + * Converts AWT image at 100% zoom into SWT one. Yours, C.O. ;-) */ public static ImageDescriptor convertImage_AWT_to_SWT(final java.awt.Image image) throws Exception { + return convertImage_AWT_to_SWT(image, 1.0); + } + + /** + * Converts AWT image at given zoom level into SWT one. Yours, C.O. ;-) + */ + public static ImageDescriptor convertImage_AWT_to_SWT(final java.awt.Image image, double zoom) throws Exception { return SwingUtils.runObjectLaterAndWait(() -> { BufferedImage bufferedImage = (BufferedImage) image; - int imageWidth = bufferedImage.getWidth(); - int imageHeight = bufferedImage.getHeight(); - Image swtImage = new Image(null, imageWidth, imageHeight); - final ImageData swtImageData = swtImage.getImageData(); try { - ImageProducer source = image.getSource(); - source.startProduction(new AwtToSwtImageConverter(bufferedImage, swtImageData)); - return ImageDescriptor.createFromImageDataProvider(zoom -> zoom == 100 ? swtImageData : null); + return ImageDescriptor.createFromImageDataProvider(new AwtImageDataProvider(bufferedImage, zoom)); } catch (Throwable e) { // fallback to ImageIO. return ImageUtils.convertToSWT(image); - } finally { - swtImage.dispose(); } }); } @@ -527,6 +537,22 @@ public static ImageDescriptor convertImage_AWT_to_SWT(final java.awt.Image image // Utils // //////////////////////////////////////////////////////////////////////////// + + /** + * Returns the native zoom level of the given component. + */ + public static double getDisplayZoom(final Component component) { + // A lof of hardcoded paint operations were added with + // 6d8cdc275b5b94a03a5a613783396f0e6db89f97, which very + // likely don't work when taking HighDPI into account. + // Without a Mac to test whether this issue is even + // relevant anymore, the check is disabled instead. + if (EnvironmentUtils.IS_MAC) { + return 1.0; + } + return component.getGraphicsConfiguration().getDefaultTransform().getScaleX(); + } + /** * Runs given runnable in dispatch thread. */ @@ -543,6 +569,53 @@ public static void runInDispatchThread(final Runnable runnable) throws Exception // AWT -> SWT converter // //////////////////////////////////////////////////////////////////////////// + + /** + * This class handles the conversion from an AWT {@link BufferedImage} to an SWT + * {@link ImageData} at any given {@code zoom} level. The original image is + * based on the current display zoom and scaled artificially to the requested + * zoom level. + */ + private static class AwtImageDataProvider implements ImageDataProvider { + /** + * Cache the image data for the individual zoom levels. + */ + private final Map imageDataAtZoom = new HashMap<>(); + private final BufferedImage image; + private final int imageZoom; + + public AwtImageDataProvider(BufferedImage image, double imageZoom) { + this.image = image; + // Convert AWT zoom to SWT zoom + this.imageZoom = (int) (imageZoom * 100); + } + + @Override + public ImageData getImageData(int zoom) { + return imageDataAtZoom.computeIfAbsent(zoom, this::createImageData); + } + + private ImageData createImageData(int zoom) { + BufferedImage imageToUse = image; + if (zoom != imageZoom) { + int scaledImageWidth = image.getWidth() * zoom / imageZoom; + int scaledImageHeight = image.getHeight() * zoom / imageZoom; + BufferedImage scaledImageToUse = new BufferedImage(scaledImageWidth, scaledImageHeight, imageToUse.getType()); + Graphics2D graphics = scaledImageToUse.createGraphics(); + graphics.drawImage(imageToUse, 0, 0, scaledImageWidth, scaledImageHeight, null); + graphics.dispose(); + imageToUse = scaledImageToUse; + } + final ImageProducer source = imageToUse.getSource(); + final int imageWidth = imageToUse.getWidth(); + final int imageHeight = imageToUse.getHeight(); + final PaletteData swtPaletteData = new PaletteData(0xFF0000, 0x00FF00, 0x0000FF); + final ImageData swtImageData = new ImageData(imageWidth, imageHeight, 24, swtPaletteData); + source.startProduction(new AwtToSwtImageConverter(imageToUse, swtImageData)); + return swtImageData; + } + } + /** * @author mitin_aa */ diff --git a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/utils/SwingScreenshotMaker.java b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/utils/SwingScreenshotMaker.java index cc0f14cbe..ea56fadae 100644 --- a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/utils/SwingScreenshotMaker.java +++ b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/utils/SwingScreenshotMaker.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011, 2024 Google, Inc. + * Copyright (c) 2011, 2025 Google, Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -28,6 +28,7 @@ import java.awt.Component; import java.awt.Container; import java.awt.Frame; +import java.awt.Graphics2D; import java.awt.Point; import java.awt.Window; import java.awt.image.BufferedImage; @@ -122,8 +123,9 @@ public void endVisit(ObjectInfo objectInfo) throws Exception { */ public void makeShots() throws Exception { SwingImageUtils.checkForDialog(m_component); - final int componentWidth = Math.max(1, m_component.getWidth()); - final int componentHeight = Math.max(1, m_component.getHeight()); + final double componentZoom = SwingImageUtils.getDisplayZoom(m_component); + final int componentWidth = Math.max(1, (int) (m_component.getWidth() * componentZoom)); + final int componentHeight = Math.max(1, (int) (m_component.getHeight() * componentZoom)); m_oldComponentLocation = m_component.getLocation(); boolean isResizable = false; // When the size of the frame exceeds the size of the screen, then the frame @@ -142,10 +144,15 @@ public void makeShots() throws Exception { final BufferedImage windowImage; // print component and its children { - int windowWidth = Math.max(1, m_window.getWidth()); - int windowHeight = Math.max(1, m_window.getHeight()); - windowImage = new BufferedImage(windowWidth, windowHeight, BufferedImage.TYPE_INT_RGB); - m_window.printAll(windowImage.getGraphics()); + double windowZoom = SwingImageUtils.getDisplayZoom(m_window); + int windowWidth = Math.max(1, (int) (m_window.getWidth() * windowZoom)); + int windowHeight = Math.max(1, (int) (m_window.getHeight() * windowZoom)); + windowImage = m_window.getGraphicsConfiguration() // + .createCompatibleImage(windowWidth, windowHeight); + Graphics2D graphics = windowImage.createGraphics(); + graphics.scale(windowZoom, windowZoom); + m_window.printAll(graphics); + graphics.dispose(); } // prepare component image if (m_component == m_window) { @@ -166,9 +173,10 @@ public void makeShots() throws Exception { componentLocation = new Point(p_component.x - p_window.x, p_component.y - p_window.y); } // copy part of window image - BufferedImage componentImage = - new BufferedImage(componentWidth, componentHeight, BufferedImage.TYPE_INT_RGB); - componentImage.getGraphics().drawImage( + BufferedImage componentImage = m_component.getGraphicsConfiguration() // + .createCompatibleImage(componentWidth, componentHeight); + Graphics2D graphics = componentImage.createGraphics(); + graphics.drawImage( windowImage, 0, 0, @@ -179,6 +187,7 @@ public void makeShots() throws Exception { componentLocation.x + componentWidth, componentLocation.y + componentHeight, m_window); + graphics.dispose(); image = componentImage; } // store image for top-level first @@ -190,7 +199,8 @@ public void makeShots() throws Exception { for (Component keyComponent : Collections.unmodifiableMap(m_componentImages).keySet()) { java.awt.Image image2 = m_componentImages.get(keyComponent); if (image2 != null) { - convertedImages.put(keyComponent, SwingImageUtils.convertImage_AWT_to_SWT(image2).createImage()); + double zoom = SwingImageUtils.getDisplayZoom(keyComponent); + convertedImages.put(keyComponent, SwingImageUtils.convertImage_AWT_to_SWT(image2, zoom).createImage()); } } // draw decorations on OS X