I am working on creating a JPanel
which supports transparency, and have come to the problem that I am not sure on how to apply the same level of transparency to all Component
s added to this panel. My code so far:
package de.uebertreiberman.project.swing;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JPanel;
@SuppressWarnings("serial")
public class JTransparancyPanel extends JPanel {
float opacity = 1.0f;
/**Constructor for JTransparentPanel
*/
public JTransparancyPanel()
{
super();
this.setOpaque(false);
}
/**Getter for opacity value of panel
*
* @return float containing opacity value of frame (0-1)
*/
public float getOpacity()
{
return opacity;
}
/**Setter for opacity value of panel
*
* @param value as float for opacity of frame (0-1)
*/
public void setOpacity(float value)
{
opacity = value;
repaint();
}
/**Converts opacity value (0-1) to opacity color (0-255)
*
* @param opacity as float opacity value (0-1)
* @return integer containing converted opacity value (0-255)
*/
public static int getOpacityColor(float opacity)
{
return (int)(opacity * 255);
}
/**Converts opacity color (0-255) to opacity value (0-1)
*
* @param opacity as integer value (0-255)
* @return float containing converted opacity value (0-1)
*/
public static float getOpacityValue(int opacity)
{
//Returns more or less the correct, capped value
//Just ignore it, it works, leave it :D
return capFloat((3.9216f*opacity)/1000f, 0.0f, 1.0f);
}
/**Returns float capped between minimum and maximum value
*
* @param value as original value
* @param min as minimum cap value
* @param max as maximum cap value
* @return float containing capped value
*/
public static float capFloat(float value, float min, float max)
{
if(value < min) value = min;
else if(value > max) value = max;
return value;
}
/**Merges color and opacity to new color
*
* @param bg as color for old color, only RGB will be used from that
* @return color with RGB from bg and A from opacity of frame
*/
Color getTransparencyColor(Color bg)
{
return new Color(getOpacityValue(bg.getRed()), getOpacityValue(bg.getGreen()), getOpacityValue(bg.getBlue()), opacity);
}
@Override
public void paintComponent(Graphics g)
{
//Draw transparent background before painting other Components
g.setColor(getTransparencyColor(getBackground()));
Rectangle r = g.getClipBounds();
g.fillRect(r.x, r.y, r.width, r.height);
//Paint other components
super.paintComponent(g);
}
}
The important part is basically at the end where I overwride the paintComponent(Graphics g)
method.
I just take the backgroundcolor
, apply the transparency to it and set it.
Now I want to make all Component children of this panel become drawn transparent as well, and I'm not quite sure what would be the most effective way of doing this.
What would you suggest?
I found a solution - basically what I already mentioned in the comments: The panel is not really painted on the screen. Instead, it is painted into an image, and this image is painted with the appropriate AlphaComposite
.
(The slider may be used to set the opacity of the panel)
Although this solution is simple, generic and should basically work with all child components, there's one thing that I don't like about it: The necessity to tweak the RepaintManager
. In order to make sure that the opacity of the panel is applied (and the child components are not painted independently, and with their full opacity), the repaint manager has to trigger a repaint of the whole component whenever a child component was repainted. I thought that there should be solution without this workaround (e.g. that this might be one of the rare cases where overriding getGaphics
on the panel might be appropriate), but no avail. Maybe the usual suspects (camickr, Hovercraft etc.) find a more elegant and concise solution.
Until then, here is the code as a MCVE:
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTree;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
public class TransparencyPanelTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
OpacityPanel opacityPanel = new OpacityPanel();
opacityPanel.setLayout(new BorderLayout());
opacityPanel.add(new JLabel("Label"), BorderLayout.NORTH);
opacityPanel.add(new JTree(), BorderLayout.CENTER);
opacityPanel.add(new JButton("Button"), BorderLayout.SOUTH);
f.getContentPane().add(opacityPanel, BorderLayout.CENTER);
JSlider slider = new JSlider(0, 100, 50);
slider.addChangeListener(e ->
opacityPanel.setOpacity(slider.getValue() / 100.0f));
f.getContentPane().add(slider, BorderLayout.SOUTH);
f.setSize(500,500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class OpacityPanel extends JPanel
{
static
{
RepaintManager.setCurrentManager(new RepaintManager()
{
@Override
public void addDirtyRegion(
JComponent c, int x, int y, int w, int h)
{
Component cc = c;
while (cc != null)
{
if (cc instanceof OpacityPanel)
{
OpacityPanel p = (OpacityPanel)cc;
super.addDirtyRegion(
p, 0, 0, p.getWidth(), p.getHeight());
}
cc = cc.getParent();
}
super.addDirtyRegion(c, x, y, w, h);
}
});
}
private float opacity = 0.5f;
private BufferedImage image;
public OpacityPanel()
{
setOpaque(false);
}
public void setOpacity(float opacity)
{
this.opacity = Math.max(0.0f, Math.min(1.0f, opacity));
repaint();
}
private void updateImage()
{
int w = Math.min(1, getWidth());
int h = Math.min(1, getHeight());
if (image == null || image.getWidth() != w || image.getHeight() != h)
{
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
Graphics2D g = image.createGraphics();
g.setColor(new Color(0,0,0,0));
g.setComposite(AlphaComposite.SrcOver);
g.fillRect(0, 0, w, h);
g.dispose();
}
@Override
protected void paintComponent(Graphics gr)
{
updateImage();
Graphics2D imageGraphics = image.createGraphics();
super.paintComponent(imageGraphics);
imageGraphics.dispose();
Graphics2D g = (Graphics2D) gr;
g.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, opacity));
g.drawImage(image, 0, 0, null);
}
}