java TrayIcon using image with transparent background - java

I am using the following code to set a tray icon in Windows and Linux. It works wonderful in Windows and works okay in Linux. In Linux (Ubuntu) I have my panel set to be (somewhat) transparent and when I add a GIF (with a transparent background) the background of the icon shows up all grey and ugly (see image, green diamond "!")....Any ideas on how to make the GIF image I am adding "keep" its transparent background?
alt text http://unarm.org/stackoverflow/panel_task.jpg
and the image I am using, if you'd like to test:
alt text http://unarm.org/stackoverflow/green_info.gif
import java.awt.*;
import java.awt.event.*;
public class TrayFun {
static class ShowMessageListener implements ActionListener {
TrayIcon trayIcon;
String title;
String message;
TrayIcon.MessageType messageType;
ShowMessageListener(
TrayIcon trayIcon,
String title,
String message,
TrayIcon.MessageType messageType) {
this.trayIcon = trayIcon;
this.title = title;
this.message = message;
this.messageType = messageType;
}
public void actionPerformed(ActionEvent e) {
trayIcon.displayMessage(title, message, messageType);
}
}
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
if (SystemTray.isSupported()) {
final SystemTray tray = SystemTray.getSystemTray();
Image image = Toolkit.getDefaultToolkit().getImage("green_info.png");
PopupMenu popup = new PopupMenu();
final TrayIcon trayIcon = new TrayIcon(image, "The Tip Text", popup);
trayIcon.setImageAutoSize(true);
MenuItem item = new MenuItem("Close");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
tray.remove(trayIcon);
}
});
popup.add(item);
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.err.println("Can't add to tray");
}
} else {
System.err.println("Tray unavailable");
}
}
};
EventQueue.invokeLater(runner);
}
}

The problem lies in the sun.awt.X11.XTrayIconPeer.IconCanvas.paint() method!
Before painting, the icon background is amateurishly cleared by simply drawing a rectangle of IconCanvas’ background color, to allow image animations.
public void paint(Graphics g) {
if (g != null && curW > 0 && curH > 0) {
BufferedImage bufImage = new BufferedImage(curW, curH, BufferedImage.TYPE_INT_ARGB);
Graphics2D gr = bufImage.createGraphics();
if (gr != null) {
try {
gr.setColor(getBackground());
gr.fillRect(0, 0, curW, curH);
gr.drawImage(image, 0, 0, curW, curH, observer);
gr.dispose();
g.drawImage(bufImage, 0, 0, curW, curH, null);
} finally {
gr.dispose();
}
}
}
}
see: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6453521

For those looking for a "real" solution, I developed a small library that is capable of displaying the tray icon honoring the transparency and also accepts SVG icons (for all platforms):
http://skype2gmail.blogspot.com/2011/05/java-tray-icon-transparency.html
It is an open source library and the source code can be found here:
https://github.com/taksan/native-tray-adapter
The library work arounds the problem by providing a JNI alternative (with GTK) when running under linux.

Chances are this problem cannot be resolved. It depends on wether Java is doing a good job in creating the tray subwindow in Linux or not. If Jave does it wrong, transparency is already lost when the image is drawn.
What is the real background value of the icon you are using? Is it the gray tone shown above? Set it to purple to see if the transparency of the image is used (Java defaults to gray background) or not.
Make sure you tried both transparency options of PNG: transparent color index as well as alpha channel. Alpha channel is very common in Linux, not so in the Windows world.
The resolution of your icon is too small. Do it in 64x64 or better 128x128. AFAIK there is no standard resolution for tray icons, and even if so, it is certainly not 16x16.
Another format you could try is SVG. Only try that after making sure that the transparency of the image is the problem (see 1).
See here for background information on this issue:
http://www.rasterman.com/index.php?page=News (scroll down to 2 February 2006)

JDIC has a tray icon, they might support transparency in linux...
https://jdic.dev.java.net/

Have you tried converting it to a .PNG (with transparency) instead? I've found they tend to be better supported by Java (In my experience)

its not that . . . this is happing because it is using the default GNOME theme for rendering the transparency - it has nothing to do with the image it self - this is an adobe air / gnome conflict - if you switch to a gnome theme were the default background is grey then it would be grey instead of white. It uses the system default image so even if it was set but the theme for the panel to have a BG image to make it look glossy like vista for example than it would do that. Adobe Air / Java doesn't know that you over road the theme default with transparency and therefor it is using the system default

Related

Is it possible to give AWT applications sharp taskbar icons in Windows 10

I'm trying to set the icon of a Java AWT application so it renders in native resolution on the Windows 10 taskbar (including when desktop scaling is set above 100%). It seems that by default, if an executable embeds an icon containing multiple sizes, Windows seems to pick a size larger than the actual size of taskbar icons and downsize it (at 100% scale it resizes the 32 pixel icon to 24, even if a 24 pixel icon is supplied, and similarly for other scales.)
I've solved this problem for C++ MFC applications by loading just the correctly sized icon as a resource and sending a WM_SETICON message to the window, which results in a nice sharp icon on the taskbar and alt-tab dialog.
smallIcon = (HICON)LoadImage( myInstance, MAKEINTRESOURCE(smallIconRes), IMAGE_ICON, smallIconSize, smallIconSize, LR_DEFAULTCOLOR );
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)smallIcon);
bigIcon = (HICON)LoadImage( myInstance, MAKEINTRESOURCE(bigIconRes), IMAGE_ICON, bigIconSize, bigIconSize, LR_DEFAULTCOLOR );
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)bigIcon);
That approach doesn't seem to work for Java applications - a WM_SETICON message with wParam set to ICON_SMALL works fine, but the equivalent with ICON_BIG is ignored.
If I try to use Java's API to set the icon, by doing this
List<Image> icons = new ArrayList<Image>();
icons.add(windowIcons.getIcon(20)); // small icons are 20x20 pixels
icons.add(windowIcons.getIcon(30)); // large are 30x30 at 125% scale
setIconImages(icons);
the correct icon is used but it appears blurry, as if something has resized it to the "expected" size and then resized it back. Left here is how it appears, right is the contents of the icon file.
So, my question is: what can I do in this Java application to make Windows render the icon I give it on the taskbar without scaling it and blurring the details?
There is indeed a scaling function called getScaledIconImage() in sun.awt.SunToolkit which is is always used when setting the icons. You must bypass this function in order to get an unaliased icon. So what you need is a replacement for java.awt.Window.setIconImages() method.
Provided several icon images Icon16x16.png, Icon24x24.png, etc. This is an example of a customSetIconImages() which puts a crisp 24x24 pixels icon in the taskbar of Windows 10.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.ImageIcon;
import java.awt.peer.WindowPeer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
#SuppressWarnings("serial")
public class MyFrame extends Frame implements WindowListener {
final Image i16, i24, i32, i48;
MyFrame() throws Exception {
i16 = Toolkit.getDefaultToolkit().getImage("Icon16x16.png");
i24 = Toolkit.getDefaultToolkit().getImage("Icon24x24.png");
i32 = Toolkit.getDefaultToolkit().getImage("Icon32x32.png");
i48 = Toolkit.getDefaultToolkit().getImage("Icon48x48.png");
addWindowListener(this);
setSize(500,300);
setTitle("Unaliased icon example");
setLayout(new FlowLayout());
setVisible(true);
}
public synchronized void customSetIconImages(java.util.List<Image> icons) throws Exception {
Field windowIcons = Class.forName("java.awt.Window").getDeclaredField("icons");
windowIcons.setAccessible(true);
windowIcons.set(this, new ArrayList<Image>(icons));
if (getPeer() != null)
updateIconImages(i24, 24, 24, i24, 24, 24);
firePropertyChange("iconImage", null, null);
}
public void updateIconImages(Image big, int bw, int bh, Image small, int sw, int sh) throws Exception {
DataBufferInt iconData = getUnscaledIconData(big, bw, bh);
DataBufferInt iconSmData = getUnscaledIconData(small, sw, sh);
WindowPeer peer = (WindowPeer) getPeer();
Method setIconImagesData = Class.forName("sun.awt.windows.WWindowPeer").getDeclaredMethod("setIconImagesData", int[].class, int.class, int.class, int[].class, int.class, int.class);
setIconImagesData.setAccessible(true);
setIconImagesData.invoke(peer, iconData.getData(), bw, bh, iconSmData.getData(), sw, sh);
}
public static DataBufferInt getUnscaledIconData(Image image, int w, int h) {
Image temporary = new ImageIcon(image).getImage();
BufferedImage buffImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffImage.createGraphics();
g2d.drawImage(temporary, 0, 0, null);
g2d.dispose();
Raster raster = buffImage.getRaster();
DataBuffer buffer = raster.getDataBuffer();
return (DataBufferInt) buffer;
}
#Override
public void windowOpened(WindowEvent arg0) {
try {
customSetIconImages(Arrays.asList(i24));
} catch (Exception e) {
System.err.println(e.getClass().getName()+" "+e.getMessage());
}
}
#Override
public void windowActivated(WindowEvent arg0) {
}
#Override
public void windowClosed(WindowEvent arg0) {
}
#Override
public void windowClosing(WindowEvent arg0) {
dispose();
}
#Override
public void windowDeactivated(WindowEvent arg0) {
}
#Override
public void windowDeiconified(WindowEvent arg0) {
}
#Override
public void windowIconified(WindowEvent arg0) {
}
public static void main(String args[]) throws Exception {
MyFrame fr = new MyFrame();
}
}
As #df778899 said, inside sun.awt.windows.WWindowPeer there are four private native methods which you can call t determine system icons size. You can combine the information returned by these methods with your own version getScaledIconImage() that performs unaliasing or not as yoou wish.
Last, note that this is a very dirty hack just for getting an unaliased icon. I've only tested in in Java 8 and Windows 10. And there are high chances that it doesn't work in newer versions of Java.
This won't be the answer you're hoping for, but this looks like a problem at the JDK level.
The window icons are handled by the sun.awt.windows.WWindowPeer class, which in turn makes a few native method calls, but there is enough to see in the source for this to point to the problem. Please read the important bit here.
Essentially, regardless of how many icon image sizes are provided, it will only pick out two sizes - for the WWindowPeer.getSysIconWidth() and getSysSmIconWidth() - to pass into the native setIconImagesData() method.
The getSysIconWidth() and getSysSmIconWidth() methods are also native, but it is possible to directly check their return values:
JFrame frame = new JFrame();
runOnPeer(frame, "getSysIconWidth");
runOnPeer(frame, "getSysIconHeight");
runOnPeer(frame, "getSysSmIconWidth");
runOnPeer(frame, "getSysSmIconHeight");
private void runOnPeer(JFrame frame, String methodName) {
//JDK8 style
//ComponentPeer peer = frame.getPeer();
//JDK11 style
Field peerField = Component.class.getDeclaredField("peer");
peerField.setAccessible(true);
Object peer = peerField.get(frame);
Method method = Class.forName("sun.awt.windows.WWindowPeer")
.getDeclaredMethod(methodName);
method.setAccessible(true);
System.out.println(methodName + "()=" + method.invoke(peer));
}
... which returns this on Windows 10 ...
getSysIconWidth()=32
getSysIconHeight()=32
getSysSmIconWidth()=16
getSysSmIconHeight()=16
As you say, clearly one of these image sizes is then being scaled for the taskbar.

Blank icon in Windows 10 notification

My java application shows its icon on the system tray using code that looks more or less like this:
Toolkit mainToolkit = Toolkit.getDefaultToolkit();
SystemTray mainTray = SystemTray.getSystemTray();
Image trayIconImage = mainToolkit.createImage(getClass().getResource(resourcePath));
TrayIcon mainTrayIcon = new TrayIcon(trayIconImage);
mainTray.add(mainTrayIcon);
Sometimes I change that icon like this:
Image newImage = mainToolkit.createImage(getClass().getResource(otherPath));
mainTrayIcon.setImage(newImage);
From time to time my app needs to show some notification (using a baloon message coming from its tray icon):
mainTrayIcon.displayMessage(someCaption, msg, TrayIcon.MessageType.NONE);
All this code is actually somehow simplified but grasps this functionality pretty well.
So everything's fine on Windows 7. But it turns out that on Windows 10 it is being shown differently. On the notification there's an icon shown on the left. It usually is my app's current tray icon, but sometimes it's just blank:
In the upper red circle (on the notification) is that blank icon which sometimes appears instead of my app's icon (in the lower red circle, on the system's tray). I have no idea why does it occur. All I know is this happens only when the app's tray icon and notification message change before first notification (which always shows its icon correctly) disappears. If the notification is shown, then fades / is closed manually AND THEN app's tray icon and notifications change, next notification (with new message that was just set) will show app's icon correctly.
Just came across this issue and found the correct solution:
mainTrayIcon.setImageAutoSize(true);
Here's a method to send a notification on Windows:
public static void sendNotification(String title, String subtitle, String pathToIcon) {
SystemTray mainTray = SystemTray.getSystemTray();
Image trayIconImage = Toolkit.getDefaultToolkit().getImage(pathToIcon);
TrayIcon mainTrayIcon = new TrayIcon(trayIconImage);
mainTrayIcon.setImageAutoSize(true);
try {
mainTray.add(mainTrayIcon);
mainTrayIcon.displayMessage(title, subtitle, TrayIcon.MessageType.NONE);
}
catch (Exception e) {
e.printStackTrace();
}
}
Calling sendNotification("Title", "Subtitle", "icons/icon-128.png"); shows
//this method to showing notification
public void notifacation(String filenamev, String tooltipv, String etats, String textv, String capationv, String messagetype) {
try {
//Obtain only one instance of the SystemTray object
SystemTray tray = SystemTray.getSystemTray();
// If you want to create an icon in the system tray to preview
Image image = Toolkit.getDefaultToolkit().createImage(filenamev);
//Alternative (if the icon is on the classpath):
//Image image = Toolkit.getDefaultToolkit().createImage(getClass().getResource("icon.png"));
TrayIcon trayIcon = new TrayIcon(image, tooltipv);
//Let the system resize the image if needed
trayIcon.setImageAutoSize(true);
//Set tooltip text for the tray icon
trayIcon.setToolTip(etats);
tray.add(trayIcon);
// Display info notification:
// trayIcon.displayMessage(capationv, textv, TrayIcon.MessageType.ERROR
trayIcon.displayMessage(capationv, textv, MessageType.valueOf(messagetype));
// Error:
// trayIcon.displayMessage("Hello, World", "Java Notification Demo", MessageType.ERROR);
// Warning:
// trayIcon.displayMessage("Hello, World", "Java Notification Demo", MessageType.WARNING);
} catch (Exception ex) {
System.err.print(ex);
}
}

How do I create a 2nd splash screen for mid-program loading?

I'm writing a java swing application which uses external resource files.
Many of the resource files are only necessary to load with certain selections.
Upon making a selection, the first window closes, the appropriate external resources are loaded, and another window opens with the resources.
The first window has a splash screen to cover the loading time.
How can I make the second window have something similar?
What I've seen involves a task occurring in the same window, which isn't viable for this project, and the Java's built-in splash screen won't start a second time (SplashScreen.getSplashScreen returns null).
In OtrosLogViewer I display first splash screen defined in MANIFEST.MF. When application is loading I render new splashscreen according to loading progress. OtrosSplah.java is calling method render to repaint splash:
private static void render() {
SplashScreen splashScreen = SplashScreen.getSplashScreen();
if (splashScreen == null) {
return;
}
Graphics2D g = splashScreen.createGraphics();
if (g == null) {
return;
}
if (version == null) {
try {
version = VersionUtil.getRunningVersion();
} catch (IOException e) {
version = "?";
}
version = "Version: " + version;
}
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setComposite(AlphaComposite.Clear);
Rectangle bounds = splashScreen.getBounds();
g.fillRect(0, 0, bounds.width, bounds.height);
g.setPaintMode();
g.setColor(Color.BLACK);
g.setFont(g.getFont().deriveFont(14f));
g.drawString(message, 20, 110);
g.drawString(version, 20, 130);
splashScreen.update();
}
You can do the same. Display first splash screen from MANIFEST.MF and later paint new one.
Instead of using the SplashScreen API, you could simply create yourself a JWindow.
Into this you can either add a bunch of components to provide the functionality you need (ie a JLabel for the background, a JLabel for the message) and make it visible while you're loading your resources.
When you done, you can simply dispose of the window.
Also, make sure, you're performing all you loading in a background thread. SwingWorker would be good for this purpose, IMHO
This answer demonstrates the concept, seek the second example...

Netbeans matisse, custom button icon property error

I have created a custom JButton where override the setIcon.
public class TestButton extends JButton {
public TestButton() {
super();
}
#Override
public void setIcon(Icon icon) {
super.setIcon(icon);
imgToBufferedImg(Toolkit.getDefaultToolkit().createImage("test.png"));
}
}
And here is the imgToBufferedImg method.
public BufferedImage imgToBufferedImg(Image image) {
if (image == null) {
return null;
}
if (image instanceof BufferedImage) {
return ((BufferedImage) image);
} else {
BufferedImage bufferedImage = new BufferedImage(
image.getWidth(null),
image.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics g = bufferedImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return bufferedImage;
}
}
I have added this component in Matisse, no problem, however, when i try to set the icon property of the button i get the error:
Failed to write the value to the property "icon"
The problem seems to come from the imgToBufferedImg since i can set the property if i remove the call to this method in setIcon. What is wrong with my image conversion method?
EDIT:
The following test succeeded:
try {
imgToBufferedImg(ImageIO.read(new FileInputStream("test.png")));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
Also i just figured out that the problem is caused by:
((ImageIcon) icon).getImage();
Running this code when the UI is ready (e.g using a SwingUtilities.invokeLater) seems to work.
The problem might be in Toolkit#createImage(). ImageIO.read() might be better. Also, it looks like you're throwing away the result from imgToBufferedImg().
no reason why
create BufferedImage inside JButtons setIcon(), there you would be to set (for JButton) Icon, ImageIcon
this BufferedImage (should be Icon, ImageIcon) is create after is added to JButton
but
method could be BufferedImage to Icon, ImageIcon
whats wrong with JButton#setIcon()
you can use paintComponent() too
Thanks to the thrashed comment:
Toolkit "operations may be performed asynchronously." Your Image may be incomplete when you try to render it.
I was able to figure out what the problem was. Straight from the setIcon method, i requested the image from the icon:
((ImageIcon) icon).getImage()
But this image is definitively incomplete. Putin my logic within the event dispatching thread did the trick.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//requesting icon images here
}
}

Display a GWT Image in a centered PopupPanel onLoad

Using GWT I am displaying an image thumbnail with a ClickHandler that then shows the full image (can be several MB) in a centered PopupPanel. In order to have it centered the image must be loaded before the popup is shown, otherwise the top-left corner of the image is placed in the middle of the screen (the image thinks it is 1px large). This is the code I am using to do this:
private void showImagePopup() {
final PopupPanel popupImage = new PopupPanel();
popupImage.setAutoHideEnabled(true);
popupImage.setStyleName("popupImage"); /* Make image fill 90% of screen */
final Image image = new Image();
image.addLoadHandler(new LoadHandler() {
#Override
public void onLoad(LoadEvent event) {
popupImage.add(image);
popupImage.center();
}
});
image.setUrl(attachmentUrl + CFeedPostAttachment.ATTACHMENT_FILE);
Image.prefetch(attachmentUrl + CFeedPostAttachment.ATTACHMENT_FILE);
}
However, the LoadEvent event is never fired, and thus the image is never shown. How can I overcome this? I want to avoid using http://code.google.com/p/gwt-image-loader/ because I do not want to add extra libraries if I can avoid it at all. Thanks.
The onLoad() method will only fire once the image has been loaded into the DOM. Here is a quick workaround:
...
final Image image = new Image(attachmentUrl + CFeedPostAttachment.ATTACHMENT_FILE);
image.addLoadHandler(new LoadHandler() {
#Override
public void onLoad(LoadEvent event) {
// since the image has been loaded, the dimensions are known
popupImage.center();
// only now show the image
popupImage.setVisible(true);
}
});
popupImage.add(image);
// hide the image until it has been fetched
popupImage.setVisible(false);
// this causes the image to be loaded into the DOM
popupImage.show();
...
Hope that helps.

Categories