[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-2d-dev
Subject:    Re: Follow up to "8313698: BaseMultiResolutionImage doesn't work as icon of java.awt.Window on Linux
From:       Andrei Rybak <rybak.a.v () gmail ! com>
Date:       2023-08-27 7:14:56
Message-ID: 87638256-294d-d360-ee61-e1ec8442c5e4 () gmail ! com
[Download RAW message or body]

Regarding my "I've forwarded a more organized list of notes from emails in this \
thread to the "Additional Information" form on bugreport.java.com." --- I don't see \
them in JBS 18 days later, so I'm reposting the notes to the mailing list, just in \
case they got lost in the JBS. And if my interleaved replies are getting missed, I'm \
putting it all in the (bad) top-posting style.

Here are some clarification regarding the original bug:

1. Unfortunately, there's some confusion in the ticket due to my poor choice
    of words for describing the _default_ icon of a `Window`.  The default icon
    depends on the vendor of the JDK, and it isn't the Duke icon for all of them.
    As can be seen from file "Capture_Windows.PNG" in the Jira issue, it can also
    be the coffee cup Java logo.
    
    I have updated the wording in the reproducer (see GitHub link and code below)
    to stop mentioning the Duke icon.
    

2. Linux can be configured with different Desktop Environments and different
    Window Managers, which can have different window decorations, which may or
    may not include the icon from java.awt.Window.
    
    My results were taken in KDE, with window decorations by KWin.
    Here's how it looks in KDE: https://i.imgur.com/s5KSpkS.png


3. The bug can be reproduced with a single `Image` inside `BaseMultiResolutionImage`.


4. The bug can be reproduced with a `BaseMultiResolutionImage` wrapped in a `List`:

        window.setIconImages(Collections.singletonList(new \
BaseMultiResolutionImage(...)));


5. The `Image`s inside `BaseMultiResolutionImage` have to loaded from resources.
    Unfortunately, the bug cannot be reproduced with an `Image` that is generated
    on-the-fly, similar to what "WindowIconUpdateOnDPIChangingTest.java" does.
        https://github.com/openjdk/jdk/blob/6864441163f946d0bec7380a2a120e31b812a6dc/t \
est/jdk/java/awt/Window/WindowIconUpdateOnDPIChanging/WindowIconUpdateOnDPIChangingTest.java
  
    
6. Some notes about my investigation with a debugger:

    > On openjdk 17.0.8: at java.desktop/sun/awt/X11/XWindowPeer.java:318,
    > IconInfo gets created in method updateIconImages(), and gets
    > initialized with `width == -1` and `height == -1`, because `null`s
    > are passed for parameter ImageObserver into java.awt.Image#getWidth
    > and java.awt.Image#getHeight, which AbstractMultiResolutionImage
    > delegates to its "base image".  But without an ImageObserver,
    > the underlying ToolkitImage has no way to update the caller about
    > the updates that might happen after the image is loaded.
    >
    > `width == -1` and `height == -1` then gets checked a couple of lines
    > down at java.desktop/sun/awt/X11/XWindowPeer.java:324, where method
    > `sun.awt.IconInfo#isValid` returns `false`.


An up-to-date reproducer with all simplifications from above applied,
and updated explanations in comments and UI labels is available on GitHub:

     https://github.com/rybak/foobar/blob/master/baz/src/main/java/swing/MultiResolutionImageBug.java
  
Full code of the reproducer also included below:

---------- BEGIN SOURCE v2 ----------
package swing;

import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BaseMultiResolutionImage;
import java.net.URL;
import java.util.List;
import java.util.Objects;

/**
  * Reproducer for <a href="https://bugs.openjdk.org/browse/JDK-8313698">JDK bug \
                8313698: BaseMultiResolutionImage
  * doesn't work as icon of java.awt.Window in KDE (with KWin) on Linux.</a>.
  * <p>
  * Tested on:
  * <ul>
  *     <li>
  *         KDE (with KWin) on Linux:
  *         <ul>
  *             <li>Java 11.0.19         ❌ Fail</li>
  *             <li>Java 17.0.7          ❌ Fail</li>
  *             <li>Java 19.0.2          ❌ Fail</li>
  *             <li>Java 20.0.1-testing  ❌ Fail</li>
  *         </ul>
  *     </li>
  *     <li>
  *         Windows:
  *         <ul>
  *             <li>11.0.17  ✅ Success</li>
  *             <li>15.0.2   ✅ Success</li>
  *             <li>18.0.2.1 ✅ Success</li>
  *         </ul>
  *     </li>
  *     <li>
  *         macOS: not applicable, because JFrame's icons aren't rendered there.
  *         Instead, only {@link Taskbar#setIconImage} is rendered in the Dock.
  *     </li>
  *     <li>
  *         Probably any Linux desktop environment & window manager pair that \
                includes icons in window decorations
  *         is affected.
  *     </li>
  * </ul>
  * <p>
  * Please note that the bug is about using {@link BaseMultiResolutionImage} that is \
                comprised itself
  * of images.
  * When same images are passed into {@link Window#setIconImages(List)} as a {@link \
                List}, without
  * {@code BaseMultiResolutionImage}, then there is no bug.
  * Not using {@code BaseMultiResolutionImage} is a workaround.
  * </p>
  * <p>
  * I've only been able to reproduce the bug with images loaded from resources (see \
                {@link #getFixedResolutionImage(String)})
  * There is no bug, if {@link Image}s are generated on the fly, like what
  * <a href="https://github.com/openjdk/jdk/blob/6864441163f946d0bec7380a2a120e31b812a \
6dc/test/jdk/java/awt/Window/WindowIconUpdateOnDPIChanging/WindowIconUpdateOnDPIChangingTest.java#L128-L147">WindowIconUpdateOnDPIChangingTest.java</a>
                
  * does.
  * </p>
  */
public class MultiResolutionImageBug {
	public static final String ICON_64 = "icon64x64.png";
	public static final String ICON_32 = "icon32x32.png";
	private int attemptsCounter;
	private JLabel counterLabel;
	private JFrame mainWindow;

	public static void main(String[] args) {
		SwingUtilities.invokeLater(() -> new MultiResolutionImageBug().go());
	}

	/**
	 * In KDE on Linux, first attempt always fails, because image isn't loaded yet,
	 * and {@code sun.awt.IconInfo#isValid} returns {@code false}.
	 */
	private void showDialog(JFrame mainWindow, boolean multi) {
		attemptsCounter++;
		counterLabel.setText(getLabelText());
		JDialog dialog = new JDialog(mainWindow, "Dialog attempt #" + attemptsCounter, \
Dialog.ModalityType.MODELESS);  \
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

		Image image = multi ? getMultiResolutionImage() : getFixedResolutionImage();
		dialog.setIconImage(image);
		// Same bug can be reproduced with:
		// dialog.setIconImages(Collections.singletonList(image));

		dialog.setLocation(200, 200);
		dialog.setSize(400, 400);
		final String explanation;
		if (!multi) {
			explanation = "Fixed resolution works fine.";
		} else {
			if (attemptsCounter == 1) {
				explanation = "In KDE on Linux, on the first attempt, the default icon (depends \
on vendor) is shown.";  } else {
				explanation = "On subsequent attempts, the correct (custom) icon is shown.";
			}
		}
		JTextArea textDisplay = new JTextArea(explanation);
		dialog.getContentPane().add(textDisplay);
		setUpEscapeKeyClosing(dialog, textDisplay);
		dialog.setVisible(true);
	}

	private void go() {
		attemptsCounter = 0;
		String windowTitle = "Bug demo on Java version " + \
System.getProperty("java.version");  System.out.println(windowTitle);
		mainWindow = new JFrame(windowTitle);
		mainWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		JPanel contentPane = new JPanel(new GridLayout(3, 1));
		mainWindow.setContentPane(contentPane);

		counterLabel = new JLabel(getLabelText());
		contentPane.add(counterLabel);

		JButton showMultiIconDialogButton = new JButton("Show dialog with \
BaseMultiResolutionImage");  showMultiIconDialogButton.addActionListener(ignored -> \
showDialog(mainWindow, true));  contentPane.add(showMultiIconDialogButton);
		JButton showFixedIconDialogButton = new JButton("Show dialog with fixed resolution \
Image");  showFixedIconDialogButton.addActionListener(ignored -> \
showDialog(mainWindow, false));  contentPane.add(showFixedIconDialogButton);

		mainWindow.setSize(400, 400);
		mainWindow.setVisible(true);
	}

	@NotNull
	private String getLabelText() {
		return "Attempts: " + attemptsCounter;
	}

	private Image getMultiResolutionImage() {
		Image icon32 = getFixedResolutionImage(ICON_32);
		return new BaseMultiResolutionImage(icon32);
		/*
		 * Just one image in BaseMultiResolutionImage is enough to reproduce
		 * the bug, but a more realistic use case is putting several images in:
		 */
		// Image icon64 = getFixedResolutionImage(ICON_64);
		// return new BaseMultiResolutionImage(icon32, icon64);
	}

	private Image getFixedResolutionImage() {
		return getFixedResolutionImage(ICON_64);
	}

	private Image getFixedResolutionImage(String filename) {
		URL url = Objects.requireNonNull(getClass().getResource(filename));
		return Toolkit.getDefaultToolkit().getImage(url);
	}

	private void setUpEscapeKeyClosing(JDialog d, JComponent c) {
		Object escapeCloseActionKey = new Object();
		c.getActionMap().put(escapeCloseActionKey, new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent e) {
				d.dispose();
			}
		});
		c.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), \
escapeCloseActionKey);  }
}
---------- END SOURCE v2 ----------

I hope this helps in the investigation and bug reproduction.

On 09/08/2023 22:37, Andrei Rybak wrote:
> On 09/08/2023 01:35, Andrei Rybak wrote:
> > Hello Aleksei, thank you for looking into this issue.
> > 
> > On 08/08/2023 22:04, Aleksei Ivanov wrote:
> > > You may want to update your test case so that it generates icons on 
> > > the fly and uses numbers to easily distinguish one from another. You 
> > > can get code from WindowIconUpdateOnDPIChangingTest.java [1].
> > 
> > This is cool, but not applicable.  With images generated on the fly,
> > the bug doesn't reproduce.  As far as I can tell from stepping
> > through this in the debugger, this has something to do with loading
> > the images from resources.
> > 
> > On openjdk 17.0.8: at java.desktop/sun/awt/X11/XWindowPeer.java:318,
> > IconInfo gets created in method updateIconImages(), and gets
> > initialized with `width == -1` and `height == -1`, because `null`s
> > are passed for parameter ImageObserver into java.awt.Image#getWidth
> > and java.awt.Image#getHeight, which AbstractMultiResolutionImage
> > delegates to its "base image".  But without an ImageObserver,
> > the underlying ToolkitImage has no way to update the caller about
> > the updates that might happen after the image is loaded.
> > 
> > `width == -1` and `height == -1` then gets checked a couple of lines
> > down at java.desktop/sun/awt/X11/XWindowPeer.java:324, where method
> > `sun.awt.IconInfo#isValid` returns `false`.
> > 
> > > You can read the commends in PR #6180 [2] where the test was added. 
> > > During the code review, it was determined the fix wasn't applicable 
> > > to Linux and macOS, so only Windows-specific changes remain.
> > 
> > The linked PR is about "8276849: Refresh the window icon on graphics
> > configuration changes".  I'm not sure if this is applicable to 8313698.
> > The issue is reproducible without graphics configuration changes,
> > on a singular monitor.
> > 
> > > As far as I know, Ubuntu doesn't show icons in the title bar of a 
> > > window, there's an app icon on the dock only, similar to macOS.
> > 
> > Ah, I see.  I should have also been more specific about that.  You're
> > probably describing Gnome Shell 3.  My results were taken in KDE,
> > with window decorations by KWin.  I haven't used other desktop
> > environments and window managers in a while, sorry for the confusion.
> > 
> > Here's how it looks in KDE: https://i.imgur.com/s5KSpkS.png
> > 
> > > Javadoc for Window.setIconImages [3] mentions, “the icons list can 
> > > contain MultiResolutionImage images”.
> > 
> > Yep. The bug report is about setIconImage (singular), and the workaround
> > is to just pass the same images as a List to setIconImages (plural),
> > without BaseMultiResolutionImage being involved.  It's included in
> > section "CUSTOMER SUBMITTED WORKAROUND".  The bug is also reproducible
> > with an exaggerated example of:
> > 
> > window.setIconImages(Collections.singletonList(new 
> > BaseMultiResolutionImage(...)));
> > 
> > I've updated the reproducer on GitHub with more details to make it
> > more clear.  The bug is also reproducible when only a single image
> > is inside BaseMultiResolutionImage.
> > 
> > Thank you for helping me clarify these details.  I hope that my original
> > point about "Duke icon" being too ambiguous with files currently attached
> > to the Jira issue won't fall through the cracks.
> > 
> > > Disclaimer: I didn't run the test case.
> > > 
> > > -- 
> > > Regards,
> > > Alexey
> > > [1] 
> > > https://github.com/openjdk/jdk/blob/6864441163f946d0bec7380a2a120e31b812a6dc/tes \
> > > t/jdk/java/awt/Window/WindowIconUpdateOnDPIChanging/WindowIconUpdateOnDPIChangingTest.java
> > >  [2] https://github.com/openjdk/jdk/pull/6180
> > > [3] 
> > > https://docs.oracle.com/en/java/javase/17/docs/api/java.desktop/java/awt/Window.html#setIconImages(java.util.List)
> > >  
> > > 
> > > On 08/08/2023 19:40, Andrei Rybak wrote: > [...]
> > > > > Because the person who reproduced the bug (Praveen Narayanaswamy) 
> > > used
> > > > two images of Duke for files icon32x32.png and icon64x64.png [1] as 
> > > input for
> > > > the _custom_ icon with BaseMultiResolutionImage, it gets really 
> > > confusing.
> > > > My reproducer, available on GitHub, just uses two images of the 
> > > numbers "32"
> > > > and "64" [2] for clarity. I have updated the wording in the 
> > > reproducer on GitHub
> > > > to stop mentioning the Duke icon.[3] > > I hope this clarifies the 
> > > ticket description. > > [1] The images in the Jira issue right now 
> > > are not of the resolution claimed
> > > > in the filenames, but the resolution doesn't matter for the bug's
> > > > reproducibility.
> > > > [2] 
> > > https://github.com/rybak/foobar/blob/master/baz/src/main/resources/swing/icon32x32.png
> > > 
> > > > and 
> > > https://github.com/rybak/foobar/blob/master/baz/src/main/resources/swing/icon64x64.png
> > > 
> > > > with honest resolutions, just to be as close as possible for 
> > > the actual use-case
> > > > [3] up-to-date reproducer on the branch master: > 
> > > https://github.com/rybak/foobar/blob/master/baz/src/main/java/swing/MultiResolutionImageBug.java \
> > > 
> 
> I've forwarded a more organized list of notes from emails in this thread to
> the "Additional Information" form on bugreport.java.com.
> 
> 


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic