绘制边框矩形以选择要录制的区域

9

bound

我如何在屏幕上绘制半透明矩形?这不能是一个JFrame,因为JFrames有通常在右上角的关闭、最小化、最大化选项。
如果确实是一个Swing组件,那么它是如何在空气中绘制的?而没有插入任何JFrame呢? 请告诉我它是什么以及我应该如何实现它...


1
这个教程可能会帮助你,如何制作半透明窗口 - nIcE cOw
3个回答

10

首先想到的方法是使用java.awt.Robot来截屏,然后将其绘制到无边框窗口中。从那里你可以简单地在上面画一个矩形。

更新示例:

... 花了一些时间 ...

enter image description here

public class SelectionRectangle {

    public static void main(String[] args) {
        new SelectionRectangle();
    }

    public SelectionRectangle() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setUndecorated(true);
                frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new BackgroundPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }

        });
    }

    public class BackgroundPane extends JPanel {

        private BufferedImage background;
        private Point mouseAnchor;
        private Point dragPoint;

        private SelectionPane selectionPane;

        public BackgroundPane() {
            selectionPane = new SelectionPane();
            try {
                Robot bot = new Robot();
                background = bot.createScreenCapture(getScreenViewableBounds());
            } catch (AWTException ex) {
                Logger.getLogger(SelectionRectangle.class.getName()).log(Level.SEVERE, null, ex);
            }

            selectionPane = new SelectionPane();
            setLayout(null);
            add(selectionPane);

            MouseAdapter adapter = new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    mouseAnchor = e.getPoint();
                    dragPoint = null;
                    selectionPane.setLocation(mouseAnchor);
                    selectionPane.setSize(0, 0);
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    dragPoint = e.getPoint();
                    int width = dragPoint.x - mouseAnchor.x;
                    int height = dragPoint.y - mouseAnchor.y;

                    int x = mouseAnchor.x;
                    int y = mouseAnchor.y;

                    if (width < 0) {
                        x = dragPoint.x;
                        width *= -1;
                    }
                    if (height < 0) {
                        y = dragPoint.y;
                        height *= -1;
                    }
                    selectionPane.setBounds(x, y, width, height);
                    selectionPane.revalidate();
                    repaint();
                }

            };
            addMouseListener(adapter);
            addMouseMotionListener(adapter);

        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.drawImage(background, 0, 0, this);
            g2d.dispose();
        }

    }

    public class SelectionPane extends JPanel {

        private JButton button;
        private JLabel label;

        public SelectionPane() {
            button = new JButton("Close");
            setOpaque(false);

            label = new JLabel("Rectangle");
            label.setOpaque(true);
            label.setBorder(new EmptyBorder(4, 4, 4, 4));
            label.setBackground(Color.GRAY);
            label.setForeground(Color.WHITE);
            setLayout(new GridBagLayout());

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            add(label, gbc);

            gbc.gridy++;
            add(button, gbc);

            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    SwingUtilities.getWindowAncestor(SelectionPane.this).dispose();
                }
            });

            addComponentListener(new ComponentAdapter() {
                @Override
                public void componentResized(ComponentEvent e) {
                    label.setText("Rectangle " + getX() + "x" + getY() + "x" + getWidth() + "x" + getHeight());
                }
            });

        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(new Color(128, 128, 128, 64));
            g2d.fillRect(0, 0, getWidth(), getHeight());

            float dash1[] = {10.0f};
            BasicStroke dashed =
                            new BasicStroke(3.0f,
                            BasicStroke.CAP_BUTT,
                            BasicStroke.JOIN_MITER,
                            10.0f, dash1, 0.0f);
            g2d.setColor(Color.BLACK);
            g2d.setStroke(dashed);
            g2d.drawRect(0, 0, getWidth() - 3, getHeight() - 3);
            g2d.dispose();
        }

    }

    public static Rectangle getScreenViewableBounds() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();

        return getScreenViewableBounds(gd);
    }

    public static Rectangle getScreenViewableBounds(GraphicsDevice gd) {
        Rectangle bounds = new Rectangle(0, 0, 0, 0);
        if (gd != null) {
            GraphicsConfiguration gc = gd.getDefaultConfiguration();
            bounds = gc.getBounds();

            Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);

            bounds.x += insets.left;
            bounds.y += insets.top;
            bounds.width -= (insets.left + insets.right);
            bounds.height -= (insets.top + insets.bottom);
        }
        return bounds;
    }
}

使用 SnipIt 示例进行更新

有些人建议使用一个透明的窗口覆盖在屏幕顶部,但实际上这是行不通的,因为透明窗口实际上不会响应鼠标点击,除非它们有一些东西可以被绘制在它们上面,从而允许捕获鼠标事件。

另外一种方法是使用类似 SnipIt 的工具。

也有人建议使用窗口作为选择机制,这是一个有效的答案,但我个人认为这不是一个合适的解决方案,因为你希望用户只需单击并拖动选择矩形(在我看来)。

enter image description here

public class SnipIt {

    public static void main(String[] args) {
        new SnipIt();
    }

    public SnipIt() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame();
                frame.setUndecorated(true);
                // This works differently under Java 6
                frame.setBackground(new Color(0, 0, 0, 0));
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new SnipItPane());
                frame.setBounds(getVirtualBounds());
                frame.setVisible(true);
            }
        });
    }

    public class SnipItPane extends JPanel {

        private Point mouseAnchor;
        private Point dragPoint;

        private SelectionPane selectionPane;

        public SnipItPane() {
            setOpaque(false);
            setLayout(null);
            selectionPane = new SelectionPane();
            add(selectionPane);
            MouseAdapter adapter = new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    mouseAnchor = e.getPoint();
                    dragPoint = null;
                    selectionPane.setLocation(mouseAnchor);
                    selectionPane.setSize(0, 0);
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    dragPoint = e.getPoint();
                    int width = dragPoint.x - mouseAnchor.x;
                    int height = dragPoint.y - mouseAnchor.y;

                    int x = mouseAnchor.x;
                    int y = mouseAnchor.y;

                    if (width < 0) {
                        x = dragPoint.x;
                        width *= -1;
                    }
                    if (height < 0) {
                        y = dragPoint.y;
                        height *= -1;
                    }
                    selectionPane.setBounds(x, y, width, height);
                    selectionPane.revalidate();
                    repaint();
                }
            };
            addMouseListener(adapter);
            addMouseMotionListener(adapter);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();

            Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
            Area area = new Area(bounds);
            area.subtract(new Area(selectionPane.getBounds()));

            g2d.setColor(new Color(192, 192, 192, 64));
            g2d.fill(area);

        }
    }

    public class SelectionPane extends JPanel {

        private JButton button;
        private JLabel label;

        public SelectionPane() {
            button = new JButton("Close");
            setOpaque(false);

            label = new JLabel("Rectangle");
            label.setOpaque(true);
            label.setBorder(new EmptyBorder(4, 4, 4, 4));
            label.setBackground(Color.GRAY);
            label.setForeground(Color.WHITE);
            setLayout(new GridBagLayout());

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            add(label, gbc);

            gbc.gridy++;
            add(button, gbc);

            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    SwingUtilities.getWindowAncestor(SelectionPane.this).dispose();
                }
            });

            addComponentListener(new ComponentAdapter() {
                @Override
                public void componentResized(ComponentEvent e) {
                    label.setText("Rectangle " + getX() + "x" + getY() + "x" + getWidth() + "x" + getHeight());
                }
            });

        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            // I've chosen NOT to fill this selection rectangle, so that
            // it now appears as if you're "cutting" away the selection
//            g2d.setColor(new Color(128, 128, 128, 64));
//            g2d.fillRect(0, 0, getWidth(), getHeight());

            float dash1[] = {10.0f};
            BasicStroke dashed =
                    new BasicStroke(3.0f,
                    BasicStroke.CAP_BUTT,
                    BasicStroke.JOIN_MITER,
                    10.0f, dash1, 0.0f);
            g2d.setColor(Color.BLACK);
            g2d.setStroke(dashed);
            g2d.drawRect(0, 0, getWidth() - 3, getHeight() - 3);
            g2d.dispose();
        }
    }

    public static Rectangle getVirtualBounds() {

        Rectangle bounds = new Rectangle(0, 0, 0, 0);

        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice lstGDs[] = ge.getScreenDevices();
        for (GraphicsDevice gd : lstGDs) {

            bounds.add(gd.getDefaultConfiguration().getBounds());

        }

        return bounds;

    }
}

是的,robot可以用来捕获屏幕。然后可以使用ImageIO将其写入磁盘,但问题是:那个神秘的矩形是什么? - An SO User
@GagandeepBali 这些人已经连续两天跟随我的问题了。我正在创建一个屏幕录制器,这些人正在帮助我。 - An SO User
@GagandeepBali 这是个解决方案。但我对它的问题在于管理鼠标事件来调整窗口大小。不是说这不是一个解决方案,而且它也取决于要求是什么。我更专注于图形渲染而不是实际手段。 - MadProgrammer
@oliholz 这个例子适用于Java 6,只需在UiManager周围捕获try-catch块以处理单个异常即可。此外,此示例默认为默认屏幕。修改为不同的屏幕也不难。 - MadProgrammer
21世纪的编程非常有趣。你不必会做任何事情,只需要会使用谷歌搜索引擎。 - phil294
显示剩余9条评论

7

将多显示器支持更新为@MadProgrammer的示例答案。

没有ExtendedState(JFrame.MAXIMIZED_BOTH)pack()

enter image description here

public SelectionRectangle() {
    EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            }

            JFrame frame = new JFrame("Test");
            frame.setUndecorated(true);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLayout(new BorderLayout());
            frame.add(new BackgroundPane());

            frame.setResizable( false );
            frame.setBounds( getScreenViewableBounds() );

            frame.setVisible(true);
        }

    });
}

public static Rectangle getScreenViewableBounds() {
    GraphicsDevice[] devices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
    int minx = Integer.MAX_VALUE;
    int miny = Integer.MAX_VALUE;
    int maxx = Integer.MIN_VALUE;
    int maxy = Integer.MIN_VALUE;
    for( GraphicsDevice device : devices ) {
        for( GraphicsConfiguration config : device.getConfigurations() ) {
            Rectangle bounds = config.getBounds();
            minx = Math.min( minx, bounds.x );
            miny = Math.min( miny, bounds.y );
            maxx = Math.max( maxx, bounds.x + bounds.width );
            maxy = Math.max( maxy, bounds.y + bounds.height );
        }
    }
    return new Rectangle( new Point(minx, miny), new Dimension(maxx - minx, maxy - miny) );
}

这是一个有趣的方法。我会在getScreenViewableBounds中使用Area,基本上将每个设备的边界/矩形相加在一起,但这只是我的想法。 - MadProgrammer

3
您可以使用一个透明的、无装饰的框架来创建一个基本的边框。
public class ScreenRectangle extends JFrame {

    public ScreenRectangle() {
        this.setUndecorated(true);
        this.setBackground(new Color(0, 0, 0, 0.25F));
        // opacity ranges 0.0-1.0 and is the fourth paramater
        this.add(new DrawPanel());
    }

    private class DrawPanel extends JPanel {

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawRect(0, 0, this.getWidth(), this.getHeight());
            // any other drawing
        } 
    }
}

框架可能还需要进行setOpaque处理,或者需要处理面板大小,但这是它的一般思路。


请调用 super.paintComponent ;) - MadProgrammer
@MadProgrammer 哎呀!修复了,谢谢。 (不过,在这种情况下,是否真的有关系呢?因为没有其他东西会在JPanel上绘制。也许我稍后会测试一下。) - FThompson
@Vulcan,创建可变大小的窗口不需要额外的代码吗?JFrame会自动处理吗? - An SO User
@Vulcan paintComponent 是负责清空 Graphics 上下文的,如果你不这样做,那么就可能会出现之前绘制组件时的绘图残留问题。 - MadProgrammer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接