Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions org.eclipse.wb.doc.user/html-src/whatsnew/v121.asciidoc
Original file line number Diff line number Diff line change
@@ -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*]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion org.eclipse.wb.doc.user/toc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<?NLS TYPE="org.eclipse.help.toc"?>
<toc label="WindowBuilder Pro User Guide" topic="html/index.html">
<topic label="Quick Start" href="html/quick_start.html" />
<topic label="What's New" href="html/whatsnew/v120.html" />
<topic label="What's New" href="html/whatsnew/v121.html" />
<topic label="Installation" href="html/installation/index.html">
<link toc="toc_installation.xml" />
</topic>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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));
}

/**
Expand All @@ -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;
Expand All @@ -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();
}
Expand Down Expand Up @@ -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();
}
});
}
Expand All @@ -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();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked long and hard at the changes done in 6d8cdc2 and I think they have aged incredibly poorly. However, without a Mac to test it with, I have no way of checking how good/bad the editor is looking.
Long story short: This keeps the current behavior of taking everything at 100% zoom.

}

/**
* Runs given runnable in dispatch thread.
*/
Expand All @@ -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<Integer, ImageData> 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
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading