Java如何通过JPanel制作屏幕截图预览

3

我正在使用Java编写一个有趣的小型测试屏幕录制程序,并希望在开始录制之前预览您的屏幕...但是我使用的方法非常缓慢和低效,涉及捕获图像、保存图像,然后通过BufferedImage读取它,并使用Graphics绘制该图像。这很慢,不适合作为“预览”。是否有一种更快速和更有效的“预览系统”?

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class MainFrame implements ActionListener, Runnable {
    //add frame components
    public static JFrame frame = new JFrame("Screen Caper - v1.0.1");
    JButton start = new JButton("record");
    JButton close = new JButton("Exit");
    JPanel preview = new JPanel();
    public static boolean running = false;
    public static boolean recording = false;
    public static boolean paused = false;
    public static String curDir = System.getProperty("user.dir");
    //get the screen width
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    double width = screenSize.getWidth();
    double height = screenSize.getHeight();
    Container a = new Container();
    Container b = new Container();
    public MainFrame() {
        frame.setSize((int)(width) - 80, (int)(height) - 80);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //setup the buttons and JPanel
        a.setLayout(new GridLayout(1, 2));
        a.add(start);
        start.addActionListener(this);
            a.add(close);
        close.addActionListener(this);
        frame.add(a, BorderLayout.NORTH);
        b.setLayout(new GridLayout(1, 2));
        b.add(preview);
        frame.add(b, BorderLayout.CENTER);
        //add anything else
        running = true;
        //set frame to visible
        frame.setVisible(true);
        run();
    }
    public static void main(String[] args) {
        new MainFrame();
    }
    public void run() {
        Graphics g = frame.getGraphics();
        while (running) {
            //draw the preview of the computer screen on the JPanel if its not recording already
            if (!recording && !paused) {
                drawPreview(g);
            }
        }
    }
    public void drawPreview(Graphics g) {
        BufferedImage image;
        try {
            image = new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
            ImageIO.write(image, "png", new File("test.png"));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        BufferedImage prevIm;
        try {
                prevIm = ImageIO.read(new File("test.png"));
                g.setColor(new Color(0, 0, 0));
                g.fillRect(preview.getX() + 3, preview.getY() + 51, preview.getWidth(), preview.getHeight() + 1);
                g.drawImage(prevIm, preview.getX() + 3, preview.getY() + 51, preview.getX() + preview.getWidth(), preview.getY() + preview.getHeight(), null);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    public void record(Graphics g) {

    }
    @Override
    public void actionPerformed(ActionEvent event) {
        if (event.getSource().equals(start)) {
            if (!recording) {
                //if the program isn't recording, then start recording
                Graphics g = frame.getGraphics();
                record(g);
                start.setText("Finish");
                recording = true;
                System.out.println("recording...");
            } else {
                //else stop recording


                start.setText("record");
                recording = false;
                System.out.println("done");
            }
        }
        if (event.getSource().equals(close)) {
            paused = true;
        int ans = JOptionPane.showConfirmDialog(null, "Woah there! You're about to quit the application\nAre you sure you want to procced?", "Caution!", JOptionPane.YES_NO_OPTION);
            if (ans == JOptionPane.YES_OPTION) {
                System.exit(0);
            } else if (ans == JOptionPane.NO_OPTION) {
                paused = false;
            }
        }
    }
}

非常感谢您的帮助!

1个回答

6
  • 不要使用getGraphics,这不是自定义绘制的方法。
  • run方法可能只是运行得太快了,考虑使用javax.swing.Timer
  • Toolkit.getDefaultToolkit().getScreenSize()仅返回“默认”屏幕,并没有考虑分屏。
  • 捕获屏幕是一个耗时的过程,你不能减少它(因为它超出了你的控制范围)。你可以设置一系列Thread来捕获桌面的某个区域。你也可以使用SwingWorkers来实现这一点,这将使更新同步回UI更容易......

看一下:

更新示例

这只是一个概念验证!你应该理解它试图做什么并从中借鉴思路......

你可以看到预览窗口在预览窗口内更改......有点奇怪......

实时预览

我在一个4480x1600的虚拟桌面上测试过。

基本上,它将桌面分成了一个4x4的网格,并为每个区域启动了一个Thread。每个线程负责捕获自己的屏幕部分。

它还缩小了结果图像并将其反馈到UI。

我一开始使用了SwingWorkers,但似乎难以限制为10个线程。你可以减少网格大小并使用SwingWorkers,它们 tend to be simpler to use and manage then raw Threads。

每个区域都被赋予一个id,这使我能够跟踪发生了什么变化。从技术上讲,你可以只添加元素到一个List中,但是如何确定什么是新的,什么是旧的?

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class PreviewDesktop {

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

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

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel implements Puzzler {

        private Rectangle virtualBounds;
        private double scale;

        private Map<Integer, PuzzlePiece> pieces;

        public TestPane() {
            virtualBounds = getVirtualBounds();
            int columns = 4;
            int rows = 4;
            pieces = new HashMap<>(columns * rows);

            int columnWidth = Math.round(virtualBounds.width / (float) columns);
            int rowHeight = Math.round(virtualBounds.height / (float) rows);

            int id = 0;
            for (int row = 0; row < rows; row++) {
                int y = virtualBounds.y + (row * rowHeight);
                for (int column = 0; column < columns; column++) {
                    int x = virtualBounds.x + (column * columnWidth);
                    Rectangle bounds = new Rectangle(x, y, columnWidth, rowHeight);
                    GrabberWorker worker = new GrabberWorker(id, this, bounds);
                    System.out.println(id);
                    id++;
                    startThread(worker);
                }
            }
        }

        @Override
        public double getScale() {
            return scale;
        }

        @Override
        public void invalidate() {
            super.invalidate();
            scale = getScaleFactorToFit(virtualBounds.getSize(), getSize());
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            for (Integer id : pieces.keySet()) {
                PuzzlePiece piece = pieces.get(id);
                Rectangle bounds = piece.getBounds();
                BufferedImage img = piece.getImage();
                g2d.drawImage(img, bounds.x, bounds.y, this);
                // If you want to see each sections bounds, uncomment below...
                //g2d.draw(bounds);
            }
            g2d.dispose();
        }

        @Override
        public void setPiece(int id, PuzzlePiece piece) {
            pieces.put(id, piece);
            repaint();
        }

        protected void startThread(GrabberWorker worker) {
            Thread thread = new Thread(worker);
            thread.setDaemon(true);
            thread.start();
        }
    }

    public class PuzzlePiece {

        private final Rectangle bounds;
        private final BufferedImage img;

        public PuzzlePiece(Rectangle bounds, BufferedImage img) {
            this.bounds = bounds;
            this.img = img;
        }

        public Rectangle getBounds() {
            return bounds;
        }

        public BufferedImage getImage() {
            return img;
        }

    }

    public interface Puzzler {

        public void setPiece(int id, PuzzlePiece piece);

        public double getScale();

    }

    public class GrabberWorker implements Runnable {

        private Rectangle bounds;
        private Puzzler puzzler;
        private int id;

        private volatile PuzzlePiece parked;
        private ReentrantLock lckParked;

        public GrabberWorker(int id, Puzzler puzzler, Rectangle bounds) {
            this.id = id;
            this.bounds = bounds;
            this.puzzler = puzzler;
            lckParked = new ReentrantLock();
        }

        protected void process(PuzzlePiece piece) {
//            puzzler.setPiece(bounds, chunks.get(chunks.size() - 1));

            puzzler.setPiece(id, piece);
        }

        protected void publish(PuzzlePiece piece) {

            lckParked.lock();
            try {
                parked = piece;
            } finally {
                lckParked.unlock();
            }
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    lckParked.lock();
                    try {
                        process(parked);
                    } finally {
                        lckParked.unlock();
                    }
                }
            });

        }

        @Override
        public void run() {
            try {
                Robot bot = new Robot();
                while (true) {
                    BufferedImage img = bot.createScreenCapture(bounds);

                    double scale = puzzler.getScale();
                    Rectangle scaled = new Rectangle(bounds);
                    scaled.x *= scale;
                    scaled.y *= scale;
                    scaled.width *= scale;
                    scaled.height *= scale;

                    BufferedImage imgScaled = getScaledInstance(img, scale);

                    publish(new PuzzlePiece(scaled, imgScaled));

                    Thread.sleep(500);

                }
            } catch (AWTException | InterruptedException exp) {
                exp.printStackTrace();
            }
        }

    }

    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;

    }

    public static double getScaleFactorToFit(Dimension original, Dimension toFit) {

        double dScale = 1d;

        if (original != null && toFit != null) {

            double dScaleWidth = getScaleFactor(original.width, toFit.width);
            double dScaleHeight = getScaleFactor(original.height, toFit.height);

            dScale = Math.min(dScaleHeight, dScaleWidth);

        }

        return dScale;

    }

    public static double getScaleFactor(int iMasterSize, int iTargetSize) {

        double dScale = (double) iTargetSize / (double) iMasterSize;
        return dScale;

    }

    public static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {

        return getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);

    }

    protected static BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint, boolean bHighQuality) {

        BufferedImage imgScale = img;

        int iImageWidth = (int) Math.round(img.getWidth() * dScaleFactor);
        int iImageHeight = (int) Math.round(img.getHeight() * dScaleFactor);

//        System.out.println("Scale Size = " + iImageWidth + "x" + iImageHeight);
        if (dScaleFactor <= 1.0d) {

            imgScale = getScaledDownInstance(img, iImageWidth, iImageHeight, hint, bHighQuality);

        } else {

            imgScale = getScaledUpInstance(img, iImageWidth, iImageHeight, hint, bHighQuality);

        }

        return imgScale;

    }

    protected static BufferedImage getScaledDownInstance(BufferedImage img,
            int targetWidth,
            int targetHeight,
            Object hint,
            boolean higherQuality) {

        int type = (img.getTransparency() == Transparency.OPAQUE)
                ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;

        BufferedImage ret = (BufferedImage) img;
        if (targetHeight > 0 || targetWidth > 0) {
            int w, h;
            if (higherQuality) {
                // Use multi-step technique: start with original size, then
                // scale down in multiple passes with drawImage()
                // until the target size is reached
                w = img.getWidth();
                h = img.getHeight();
            } else {
                // Use one-step technique: scale directly from original
                // size to target size with a single drawImage() call
                w = targetWidth;
                h = targetHeight;
            }

            do {
                if (higherQuality && w > targetWidth) {
                    w /= 2;
                    if (w < targetWidth) {
                        w = targetWidth;
                    }
                }

                if (higherQuality && h > targetHeight) {
                    h /= 2;
                    if (h < targetHeight) {
                        h = targetHeight;
                    }
                }

                BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
                Graphics2D g2 = tmp.createGraphics();
                g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
                g2.drawImage(ret, 0, 0, w, h, null);
                g2.dispose();

                ret = tmp;
            } while (w != targetWidth || h != targetHeight);
        } else {
            ret = new BufferedImage(1, 1, type);
        }
        return ret;
    }

    protected static BufferedImage getScaledUpInstance(BufferedImage img,
            int targetWidth,
            int targetHeight,
            Object hint,
            boolean higherQuality) {

        int type = BufferedImage.TYPE_INT_ARGB;

        BufferedImage ret = (BufferedImage) img;
        int w, h;
        if (higherQuality) {
            w = img.getWidth();
            h = img.getHeight();
        } else {
            w = targetWidth;
            h = targetHeight;
        }

        do {
            if (higherQuality && w < targetWidth) {
                w *= 2;
                if (w > targetWidth) {
                    w = targetWidth;
                }
            }

            if (higherQuality && h < targetHeight) {
                h *= 2;
                if (h > targetHeight) {
                    h = targetHeight;
                }
            }

            BufferedImage tmp = new BufferedImage(w, h, type);
            Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();

            ret = tmp;
            tmp = null;
        } while (w != targetWidth || h != targetHeight);
        return ret;
    }
}

现在,如果你感到非常有冒险精神,可以更新这个想法,以便如果某个部分的底层图像没有更改并且比例没有更改,则不向UI发送该信息,这可能有助于减少一些开销...不过,我没有代码可以做到这一点 ;)

谢谢!它运行得很好!是的,那可能会有助于减少过多的内存使用。再次感谢。 - Bryce Hahn

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