2

I am currently in the process of developing an app, that needs the functionality to select screen area. I've come up with creating a transparent, undecorated, fullscreen JFrame, and adding a translucent, non-opaque JPanel inside of it, where a half-translucent dark background, as well as the selection is painted.

And while the idea (and the code) runs fine on Windows, its not the same story on linux, where the background of the JPanel does not seem to be cleared upon calling repaint() (even though i tell it to via various methods) - upon each repaint method, the background and the component get darker and darker, etc.

Here's the MVCE:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ExampleFrame extends JFrame{


    private ExamplePanel selectionPane;

    public ExampleFrame(){
        this.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {

            }

            @Override
            public void keyPressed(KeyEvent e) {

            }

            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    ExampleFrame.this.dispatchEvent(new WindowEvent(ExampleFrame.this, WindowEvent.WINDOW_CLOSING));
                }
            }
        });


        this.setExtendedState(JFrame.MAXIMIZED_BOTH);

        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        this.setSize(screenSize);

        this.setUndecorated(true);

        this.setBackground(new Color(255, 255, 255, 0));

        populate();

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setType(Window.Type.UTILITY);
        this.setVisible(true);
    }

    private void populate(){
        this.selectionPane = new ExamplePanel();
        this.setContentPane(selectionPane);
    }

    public static void main(String[] args){
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ExampleFrame();
            }
        });
    }

    public static class ExamplePanel extends JPanel{

        private static Color bg = new Color(0,0,0,0.5f);

        private int sx = -1, sy = -1, ex = -1, ey = -1;

        public ExamplePanel(){

            MouseAdapter mouseAdapter = new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    sx = sy = ex = ey = -1;

                    sx = e.getX();
                    sy = e.getY();
                    repaint();
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    ex = e.getX();
                    ey = e.getY();
                    repaint();
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    ex = e.getX();
                    ey = e.getY();
                    repaint();
                }
            };

            this.addMouseListener(mouseAdapter);
            this.addMouseMotionListener(mouseAdapter);

            this.setDoubleBuffered(false);
            this.setOpaque(false);
            this.setBackground(bg);
        }

        @Override
        public void paintComponent(Graphics g){
            Graphics2D g2 = (Graphics2D)g.create();

            g2.setComposite(AlphaComposite.Clear);
            g2.setBackground(new Color(255, 255, 255, 0));
            g2.fillRect(0, 0, getWidth(), getHeight());
            //g2.clearRect(0, 0, getWidth(), getHeight()); //neither of them work

            g2.setComposite(AlphaComposite.Src.derive(.5f));
            g2.setPaint(getBackground());
            g2.fillRect(0, 0, getWidth(), getHeight());

            g2.setComposite(AlphaComposite.Src.derive(1f));
            g2.setPaint(Color.WHITE);
            g2.drawString("Press Escape to exit", 10, 20);

            if(!(sx == -1 || sy == -1 || ex == -1 || ey == -1)){

                int asx = Math.min(sx, ex);
                int asy = Math.min(sy, ey);

                int w = Math.abs(ex - sx);
                int h = Math.abs(ey - sy);

                g2.setComposite(AlphaComposite.Src);
                g2.setPaint(new Color(255, 255, 255, 0));
                g2.fillRect(asx, asy, w, h);

                g2.setPaint(new Color(0, 0, 0, 1));
                g2.fillRect(asx, asy, w, h);

                g2.setComposite(AlphaComposite.SrcOver);
                g2.setStroke(new BasicStroke(2));
                g2.setPaint(new Color(1, 1, 1, 0.15f));
                g2.drawRect(asx-1,asy-1, w+2, h+2);
            }
        }
    }

}

Any ideas as to what might cause this? Or maybe this is a bug with Java on linux? I had tested this under Windows 10, and Ubuntu 14.04 LTS as well as unknown version of Arch Linux running with KDE gui (tested by a friend)

EDIT: also tested under OSX (Yosemite & El capitan), both worked fine.

themorfeus
  • 277
  • 1
  • 3
  • 17
  • Any reason your setting double buffering to false? – MadProgrammer Jan 31 '16 at 00:41
  • Besides someone on the internet telling that it might help - no. – themorfeus Jan 31 '16 at 00:47
  • Okay, double buffering will prevent the updates from flickering and in my testing, seem to make it render faster when enabled. So I've been running you code on MacOS, but can't seem to replicate the issues, however, you really should be calling `super.paint` and the "clearing" feature is redundant. For a different approach, you could use something like [this](http://stackoverflow.com/questions/23709070/how-to-disable-java-awt-graphics-fillrectint-x-int-y-int-width-int-heights/23709320#23709320) – MadProgrammer Jan 31 '16 at 00:57
  • or [this](http://stackoverflow.com/questions/13948122/drawing-a-bounding-rectangle-to-select-what-area-to-record/13948198#13948198) – MadProgrammer Jan 31 '16 at 00:57
  • The app runs fine on MacOS, there were never issues there. Also, the app has to show live screen behind the frame, so taking a screenshot with robot each frame is not an option. – themorfeus Jan 31 '16 at 13:07
  • I was thinking more about using `Area` rather than using `AlphaComposite`s. So what OSs exhibited issues? – MadProgrammer Jan 31 '16 at 20:09
  • The only OS that has problems is Linux. – themorfeus Jan 31 '16 at 23:49
  • TheMorfeus, I got same problem on Ubuntu as you. Mac/Windows works fine. :( Have you found any solution? – Bill Lin Apr 26 '18 at 18:28

3 Answers3

3
this.setBackground(new Color(255, 255, 255, 0));

If you want a component completely transparent then just use:

component.setOpaque( false );

This tells Swing to look for the parent component and paint it first so you don't get the painting artifacts.

private static Color bg = new Color(0,0,0,0.5f);

If you want semi-transparent backgrounds then need to do custom coding since Swing doesn't support this.

Check out Backgrounds With Transparency for more information on this topic and a couple of solutions.

One is to do your own custom painting with code like:

JPanel panel = new JPanel()
{
    protected void paintComponent(Graphics g)
    {
        g.setColor( getBackground() );
        g.fillRect(0, 0, getWidth(), getHeight());
        super.paintComponent(g);
    }
};

The other solution is a reusable class that can be used with any component so you don't need to customize every component. panel.setOpaque(false); // background of parent will be painted first panel.setBackground( new Color(255, 0, 0, 20) ); frame.add(panel);

camickr
  • 321,443
  • 19
  • 166
  • 288
  • You're telling me swing cant do that, but you redirect me to a site which shows the same code, only using ints and not floats. Also, JFrames do not have setOpaque method. – themorfeus Jan 30 '16 at 00:56
  • @TheMorfeus, `upon each repaint method, the background and the component get darker and darker, etc.` - that is the exact same behaviour described in the link which is why I directed you to the link. I guess the difference is that you are also using a transparent frame. Sorry it didn't help. – camickr Jan 30 '16 at 01:43
  • @TheMorfeus Calling `setBackground` and passing it alpha based color works ONLY on a `JFrame`, it won't work other components, you need to fake it by first making the component transparent and then painting the background color yourself – MadProgrammer Jan 30 '16 at 06:18
  • @MadProgrammer i'm doing that though, im not using standard paintComponent at all. – themorfeus Jan 30 '16 at 09:29
  • @TheMorfeus, have you tried simplifying your painting code to see if you can just paint a transparent background? The code In the link I gave you doesn't use a Composite when painting the background. A MCVE should be the simplest possible code. You have all kind of composite related code. Maybe that is the problem. I can't test since I use Windows and you said it isn't a problem. – camickr Jan 30 '16 at 16:25
  • @TheMorfeus You painting the background twice, once with an alpha color and once with `AlphaComposite` – MadProgrammer Jan 30 '16 at 19:58
  • @camickr Yes, i did that, actually before i had even heard about `AlphaComposite`. . The result on linux was still the same, but the app ran quite a bit slower (on all systems) - using the method here, the app runs much smoother. – themorfeus Jan 30 '16 at 22:21
  • @MadProgrammer the first time i am drawing the background, it has an alpha of 0 and `AlphaComposite` of Clear, which results in cleaning the specified area (simillar to `Graphics.clearRect`) – themorfeus Jan 30 '16 at 22:22
  • @TheMorfeus Well, the question there is why? Make the component transparent and simply call super.paintComponent – MadProgrammer Jan 30 '16 at 22:29
  • @MadProgrammer as mentioned in a comment ot whiskeyspider, i had already tried that, and that doesn't change anything. – themorfeus Jan 31 '16 at 00:09
  • @TheMorfeus But 1- it reduces your code; 2- preservers the paint chain – MadProgrammer Jan 31 '16 at 00:40
  • Sure, i can add that, but the error is still reproduced. – themorfeus Jan 31 '16 at 00:54
2

It's difficult to know the exact cause of the problem without been able to replicate it. There are a number of areas of concern within the code...

  • Not honouring the paint call chain by not calling super.paintComponent
  • The use of setDoubleBuffered(false)
  • The use of this.setBackground(bg); on a JPanel and passing an alpha based color to it
  • The extensive use of AlphaComposite and it's scrupulous use to try and clear the Graphics context

The basic course of action would be to simplify the paint process until such a time as you can identify the action or combination of actions which are causing the issues.

Or take another approach. Rather than using a combination of different AlphaComposite settings, you might consider just using a Area and subtract the area you want exposed from it....

Cut

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.geom.Area;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ExampleFrame extends JFrame {

    private ExamplePanel selectionPane;

    public ExampleFrame() {
        this.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {

            }

            @Override
            public void keyPressed(KeyEvent e) {

            }

            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    ExampleFrame.this.dispatchEvent(new WindowEvent(ExampleFrame.this, WindowEvent.WINDOW_CLOSING));
                }
            }
        });

        this.setExtendedState(JFrame.MAXIMIZED_BOTH);

        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        this.setSize(screenSize);

        this.setUndecorated(true);

        this.setBackground(new Color(255, 255, 255, 0));

        populate();

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setType(Window.Type.UTILITY);
        this.setVisible(true);
    }

    private void populate() {
        this.selectionPane = new ExamplePanel();
        this.setContentPane(selectionPane);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ExampleFrame();
            }
        });
    }

    public static class ExamplePanel extends JPanel {

        private static Color bg = new Color(0, 0, 0);

        private int sx = -1, sy = -1, ex = -1, ey = -1;

        public ExamplePanel() {

            MouseAdapter mouseAdapter = new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    sx = sy = ex = ey = -1;

                    sx = e.getX();
                    sy = e.getY();
                    repaint();
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    ex = e.getX();
                    ey = e.getY();
                    repaint();
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    ex = e.getX();
                    ey = e.getY();
                    repaint();
                }
            };

            this.addMouseListener(mouseAdapter);
            this.addMouseMotionListener(mouseAdapter);

            this.setOpaque(false);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g.create();

            Area area = new Area(new Rectangle(0, 0, getWidth(), getHeight()));

            if (!(sx == -1 || sy == -1 || ex == -1 || ey == -1)) {

                int asx = Math.min(sx, ex);
                int asy = Math.min(sy, ey);

                int w = Math.abs(ex - sx);
                int h = Math.abs(ey - sy);

                area.subtract(new Area(new Rectangle(asx - 1, asy - 1, w + 2, h + 2)));
            }
            g2.setComposite(AlphaComposite.Src.derive(.25f));
            g2.setPaint(bg);
            g2.fill(area);

            g2.setComposite(AlphaComposite.Src.derive(1f));
            g2.setPaint(Color.WHITE);
            g2.drawString("Press Escape to exit", 10, 20);
            g2.dispose();
        }
    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Even with all of the problems with my code, plus the Area, it still does not work on Linux and fails to repaint. The problems with my code are the results of many experiments, and it doesnt work regardless of wheter they are there, or not – themorfeus Jan 31 '16 at 23:48
  • Okay, so strip back you code. Start with a non-transparent window, see if you can make that work. If that works, then include the transparent window, if that fails, at least you've identified the problem or discounted a part of it. It may just be an issue with Linux, can't say, not have a linux install to test with. I would suggest that using a `Area` this way is slightly less problematic, as you're unlikely to confusing the `AlphaComposite` settings – MadProgrammer Jan 31 '16 at 23:58
  • Just did that, and everything worked fine until i added transparency on JFrame. So i guess there's really a problem in Java on Linux. Even the code from [here](https://docs.oracle.com/javase/tutorial/uiswing/misc/trans_shaped_windows.html) (Per-pixel translucency) does not work correctly and does not clear the background of the transparent window. Welp. Also, using `Area` is not acceptable, since `AlphaComposite`/`Graphics.clearRect` also clear the text that is drawn on the panel, which `Area` does not. – themorfeus Feb 02 '16 at 19:00
  • Well, since I don't know what you're actually trying to achieve, it's difficult to make a suggestion ;) – MadProgrammer Feb 02 '16 at 20:09
0

Be sure to call super.paintComponent() if you want to clear the background.

    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g.create();
martinez314
  • 12,162
  • 5
  • 36
  • 63
  • 1
    oh, right. I removed that from my code, because someone told someone else that it might not work because of that. Yeah, i've ventured far and wide looking for that answer. TL;DR: doesn't change anything. – themorfeus Jan 29 '16 at 21:48