java用JPanel制作屏幕截图预览

时间:2014-02-05 04:48:23

标签: java swing bufferedimage performance screen-recording

我正在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 个答案:

答案 0 :(得分:6)

  • 不要使用getGraphics,这不是自定义绘画的方式。
  • 您的run方法可能只是快速运行,请考虑使用javax.swing.Timer代替
  • Toolkit.getDefaultToolkit().getScreenSize()仅返回“默认”屏幕,不考虑拆分屏幕
  • 捕捉屏幕是一个耗时的过程,你可以做很少的事情来减少它(因为它超出你的控制范围)。您“可以”设置一系列Thread,其作用是捕获桌面的给定部分。您还可以使用SwingWorkers实现此目的,这样可以更轻松地将更新同步回UI ...

看看:

更新了示例

这只是一个概念证明!你应该了解它正在尝试做什么,并借鉴它......

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

live preview

它在4480x1600的虚拟桌面上进行了测试。

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

它还会缩小生成的图像并将其反馈给用户界面。

我从SwingWorker开始,但似乎很难被限制为10个线程。您可以减小网格大小并使用SwingWorker,它们往往更容易使用和管理原始Thread

每个部分都有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,可能有助于减少一些开销......不,我没有代码可以做到这一点;)