11

I want to create a fully transparent background for a Frame (or JFrame) and have it show a transparent animation. I managed to get it working in Windows 7 x64 but the same code does not run on my Linux (Lubuntu x64 15.04).

The code below shows what I'm trying to achieve--just copy & paste it. I just want the little rectangle to move across the screen without leaving a trail.

static int  a   = 0;

public static void main(String[] args) {
    JFrame f = new JFrame();
    f.setUndecorated(true);
    f.setBackground(new Color(0, 0, 0, 0));
    f.setVisible(true);
    f.setSize(512, 512);
    f.add(new JPanel() {
        @Override
        public void paintComponent(Graphics gr) {
            Graphics2D g = (Graphics2D)gr;
            g.setBackground(new Color(0, 0, 0, 0));
            g.clearRect(0, 0, 512, 512);
            g.drawRect(a, a++, 2, 2);
        }
    });

    while(true) {
        try {
            Thread.sleep(30);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
        f.repaint();
    }
}

What I want to achieve (as shown in Windows) and what I get with Lubuntu 15.04:

Desired Animation          Lubuntu Animation

I just want to see the little square move just like what's shown on Windows 7--I don't want to see a trail.

Please don't give me the link of Oracle's transparency and window documentation--I've gone over it all thrice.

What I've tried:

  • Graphics2D's 'copyArea()' of a transparent space. (This used to work AFAIK but no longer does)
  • GlassPane
  • AlphaComposite
  • setPaint()

Please please just test out your thoughts/code first. A lot of the "this should work" stuff I have already tried and does not seem to... All help is greatly appreciated.

Roland
  • 612
  • 5
  • 17
  • After many hours and hours I've found it: It's a back-buffer issue. I found a way to fix it too, but I don't have much time now so I will post it later today. Still need to test it on Windows too. My solution does seem to flicker somewhat so I will probably see if I can fix that too. Anyway I would really appreciate some help with the testing of the new solution as I only have Windows 7 x64 and Lubuntu 15.04 available to me. Thanks guys. – Roland Jul 15 '15 at 14:15
  • cant wait for the answer, and I will try it to on my computers :D – JetStream Jul 17 '15 at 06:49
  • @JetStream I've posted an answer below. It works for Linux and Windows 7 for me but I don't own a Mac so I can't test that one... – Roland Jul 17 '15 at 09:35

3 Answers3

3

For reference, here's a minimal complete example, suitable for cross-platform testing. Note that

  • On some platforms, e.g. Ubuntu, a completely transparent background is not seen as opaque; a small, non-zero alpha value is a typical work-around.

  • Swing GUI objects should be constructed and manipulated only on the event dispatch thread.

  • Use java.swing.Timer, which runs on the event dispatch thread, to pace the animation.

  • Don't use setPreferredSize() when you really mean to override getPreferredSize().

image

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 * @see https://stackoverflow.com/a/31328464/230513
 */
public class TransparentAnimation {

    private static final Color tranparentBlack = new Color(0, 0, 0, 1);

    private void display() {
        JFrame f = new JFrame("Test");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setUndecorated(true);
        f.setBackground(tranparentBlack);
        f.add(new JPanel() {
            int x, y;
            Timer t = new Timer(10, (ActionEvent e) -> {
                x = (x + 1) % getWidth();
                y = (y + 1) % getHeight();
                repaint();
            });

            {
                setBackground(tranparentBlack);
                t.start();
            }

            @Override
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
                g.fillOval(x, y, 16, 16);
            }

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(320, 240);
            }
        });
        f.add(new JLabel(System.getProperty("os.name") + "; v"
            + System.getProperty("os.version")), BorderLayout.SOUTH);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new TransparentAnimation()::display);
    }
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • [Screenshots](http://meta.stackoverflow.com/questions/99734/how-do-i-create-a-screenshot-to-illustrate-a-post) for other platforms/versions welcome. – trashgod Jul 09 '15 at 21:27
  • Thx for the reply but this does not answer my question. I know how to create a transparent frame, which my example code already accomplishes. I want to know how to create an animation. I've just tested the code I posted on a Windows machine and it appears that my code runs fine, so for some reason, it does not want to work on my Linux. Not sure if it's a Linux, Ubuntu, or LXDE issue then... – Roland Jul 10 '15 at 07:43
  • @Roland: Ah, I misinterpreted your example, which appears to modify `a` on one thread and access it on another. More above. – trashgod Jul 10 '15 at 10:46
  • Not working for Windows or Linux :( http://i62.tinypic.com/2lo2nvc.png http://i62.tinypic.com/slqiqa.png – Roland Jul 10 '15 at 14:37
  • Ouch; it works on Mac OS X 10.10 and Ubuntu 14.04.2, but I'm emulating the latter; you may have to `setOpaque(false)` and fill the whole panel explicitly. – trashgod Jul 10 '15 at 14:57
3

Basically this issue is OS-related. What works for Windows will not work for Linux and vice versa.

For some reason, Linux only allows animated per-pixel-transparency when setting up a BufferStrategy. However, this solution fails on Windows. As a result I have come up with the following code which picks the correct algorithm based on the OS:

static int a = 0;

public static void main(String[] args) {
    JFrame f = new JFrame();
    JPanel p = new JPanel() {
        @Override
        public void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            g2d.setBackground(new Color(255, 255, 255, 0));
            g2d.clearRect(0, 0, f.getWidth(), f.getHeight());
            g2d.drawRect(a, a++, 2, 2);
        }
    };
    f.add(p);
    f.setUndecorated(true);
    f.setBackground(new Color(255, 255, 255, 0));
    f.setSize(512, 512);
    f.setVisible(true);
    f.createBufferStrategy(2);

    BufferStrategy bs = f.getBufferStrategy();
    while (true) {
        try {
            Thread.sleep(33);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (System.getProperty("os.name").contains("indows ")) {
            p.repaint();
        } else {
            Graphics g = null;
            do {
                try {
                    g = bs.getDrawGraphics();
                    p.update(g);
                } finally {
                    g.dispose();
                }
                bs.show();
            } while (bs.contentsLost());
            Toolkit.getDefaultToolkit().sync();
        }
    }
}

This code works for my Windows 7 x64 and my Lubuntu 15.04 x64. Please try out this code out yourself and see if it works for you. I myself don't own a Mac so if someone would please test it for me I would be very grateful. If it does not work for anyone, please let me know.

This is what you're supposed to see:

enter image description here

Roland
  • 612
  • 5
  • 17
  • Works on Mac OS X with `.contains("Mac")`, but see also [*Initial Threads*](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html). – trashgod Jul 17 '15 at 09:55
  • @trashgod So Mac also needs to call p.repaint()? – Roland Jul 17 '15 at 10:43
  • Yes, AFAIK; I call `repaint()` from a `javax.swing.Timer`, which schedules runnables on the EDT, in my [example](http://stackoverflow.com/a/31328464/230513). – trashgod Jul 17 '15 at 11:12
  • [`isWindowTranslucencySupported()`](https://docs.oracle.com/javase/tutorial/uiswing/misc/trans_shaped_windows.html#capability) on your target platform? What kind? – trashgod Jul 19 '15 at 11:11
  • All types give true for both my Windows and Linux. These are my machines (not VMs): Lubuntu 15.04 (based on Ubuntu 15.04) x64, HD Graphics 5500, Linux kernel 3.19.0, LXDE Desktop Environment. Windows 7 x64, AMD R9 270 with Catalyst Version 15.7. – Roland Jul 21 '15 at 08:28
2

If we extend JFrame, set undecorated to true, and override paint with, we can make a transparent JFrame.

try this,

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class TestTransparentFrame {

private class PaintPanel extends JPanel {
    private List<Point> points = new ArrayList<Point>();

    public PaintPanel() {
        setOpaque(false);
        MouseAdapter adapter = new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                points.clear();
                repaint();
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                points.add(e.getPoint());
                repaint();
            }
        };
        addMouseListener(adapter);
        addMouseMotionListener(adapter);
        setBorder(BorderFactory.createLineBorder(Color.GREEN));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (points.size() > 1) {
            g.setColor(Color.RED);
            Point p1 = points.get(0);

            for (int i = 1; i < points.size(); i++) {
                Point p2 = points.get(i);
                g.drawLine(p1.x, p1.y, p2.x, p2.y);
                p1 = p2;
            }
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(700, 500);
    }
}

protected void createAndShowGUI() throws MalformedURLException, IOException {
    JFrame frame = new JFrame("Test transparent painting");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setUndecorated(true);
    frame.setBackground(new Color(0, 0, 0, 50));
    frame.add(new PaintPanel());
    frame.pack();
    frame.setVisible(true);
}

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            try {
                new TestTransparentFrame().createAndShowGUI();
            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });

}

}
  • Thx for the reply but I've already tried this. If you could edit your answer and paste working code that would be great. – Roland Jul 09 '15 at 21:02
  • Thx for posting your code but this particular example does not apply to the problem I am having. I would want the lines/trail to disappear when moving the mouse. The problem is that the JFrame does not clear its screen contents before the JPanel is re-drawn. In other words, the JPanel keeps drawing over itself... that's the issue I'm having, but thanks for the help :) – Roland Jul 10 '15 at 09:06