在 Java 应用程序中镜像计算机屏幕

Mirror the computer screen in a Java application

我想制作一个 java 应用程序来镜像计算机屏幕(Windows 10)并显示它本身。它并不太复杂(我认为):它只是一个应用程序,可以捕获计算机屏幕上显示的所有内容并自行显示。但我不想镜像主屏幕。我想镜像副屏,投影屏。

---> 我需要这个的原因: 我在距离我将近 20 米的教堂的大屏幕上投影内容,很难看到内容,而且没有 space 可以在我旁边放置另一个显示器...这就是为什么我需要一个应用程序在主屏幕上显示正在展示的内容。

我已经在网上搜索过它,但没有找到满意的结果...我找到的最多的是屏幕截图,并逐字记录屏幕并另存为 MP4 或您喜欢的其他扩展名。

有人可以帮我吗?

从概念上讲,这个想法非常简单。您想使用 Robot 的实例来捕获屏幕快照。

不过,您需要做的第一件事是获取要捕获的屏幕“区域”,这并不像听起来那么容易,因为 Java 似乎没有提供“屏幕名称”(在 Windows GraphicsDevice#getIDstring 上可能 return 名称,但在 MacOS 上它没有)

所以,我开始尝试查看屏幕分辨率,并试图找出我想要的屏幕...

GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice lstGDs[] = ge.getScreenDevices();
for (GraphicsDevice gd : lstGDs) {
    System.out.println(gd.getDisplayMode());
}

我也可以使用 getBounds 并尝试弄清楚每个屏幕的位置,但不管用什么。

获得此信息后,您可以使用 GraphicsDevice#getBounds 获取屏幕的物理区域,您可以将其提供给 Robot

现在,您需要一些方法来生成和使用这些快照。您需要了解 Swing 不是线程安全的并且是单线程的。这意味着您无法从事件调度线程捕获快照,并且不应尝试从事件调度线程外部更新 UI。有关详细信息,请参阅 Concurrency in Swing

考虑到这一点,我决定使用 SwingWorker。它为我们完成了大部分繁重的工作,并提供了简化的工作流程。有关详细信息,请参阅 Worker Threads and SwingWorker

好了,接下来要解决的问题,就是如何渲染画面了。基本概念就是将图像绘制到组件上,这并不难,难的部分是缩放图像。

关于这个主题已经有很多讨论,但你可以看看 How do I resize images inside an application when the application window is resized? and Java: maintaining aspect ratio of JPanel background image

第一个提出了“缩放以适合”和“缩放以填充”算法的概念,第二个提出了一种更好的缩放图像的方法,然后使用 Image#getScaledInstance(示例使用)作为 getScaledInstance 并不总是呈现最佳结果。您还可以查看 Quality of Image after resize very low -- Java,其中提供了更多想法和讨论。

import java.awt.AWTException;
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.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;

class Main {

    public static void main(String[] args) {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice lstGDs[] = ge.getScreenDevices();
        for (GraphicsDevice gd : lstGDs) {
            System.out.println(gd.getDisplayMode());
        }

        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private CaptureWorker worker;
        private BufferedImage snapshot;

        public TestPane() {
        }

        @Override
        public void addNotify() {
            super.addNotify();
            startCapture();
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            stopCapture();
        }

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

        protected void startCapture() {
            try {
                stopCapture();
                GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
                GraphicsDevice gd = ge.getScreenDevices()[0];
                worker = new CaptureWorker(gd, new CaptureWorker.Observer() {
                    @Override
                    public void imageAvaliable(CaptureWorker source, BufferedImage img) {
                        TestPane.this.snapshot = img;
                        repaint();
                    }
                });
                worker.execute();
            } catch (AWTException ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        protected void stopCapture() {
            if (worker == null) {
                return;
            }
            worker.stop();
            worker = null;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (snapshot != null) {
                double scaleFactor = Math.min(1d, getScaleFactorToFill(new Dimension(snapshot.getWidth(), snapshot.getHeight()), getSize()));
                int scaleWidth = (int) Math.round(snapshot.getWidth() * scaleFactor);
                int scaleHeight = (int) Math.round(snapshot.getHeight() * scaleFactor);

                Image imageToRender = snapshot.getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH);
                int x = (getWidth() - imageToRender.getWidth(this)) / 2;
                int y = (getHeight() - imageToRender.getHeight(this)) / 2;

                g2d.drawImage(imageToRender, x, y, this);
            }
            g2d.dispose();
        }

        public double getScaleFactor(int iMasterSize, int iTargetSize) {
            double dScale = 1;
            dScale = (double) iTargetSize / (double) iMasterSize;

            return dScale;
        }

        public 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 double getScaleFactorToFill(Dimension masterSize, Dimension targetSize) {
            double dScaleWidth = getScaleFactor(masterSize.width, targetSize.width);
            double dScaleHeight = getScaleFactor(masterSize.height, targetSize.height);

            double dScale = Math.max(dScaleHeight, dScaleWidth);

            return dScale;
        }

    }

    public class CaptureWorker extends SwingWorker<Void, BufferedImage> {

        public interface Observer {

            public void imageAvaliable(CaptureWorker source, BufferedImage img);
        }

        private AtomicBoolean keepRunning = new AtomicBoolean(true);
        private Robot bot;
        private Rectangle captureBounds;

        private final Duration interval = Duration.ofMillis(250);

        private Observer observer;

        public CaptureWorker(GraphicsDevice device, Observer observer) throws AWTException {
            captureBounds = device.getDefaultConfiguration().getBounds();
            this.observer = observer;
            bot = new Robot();
        }

        public void stop() {
            keepRunning.set(false);
        }

        @Override
        protected void process(List<BufferedImage> chunks) {
            BufferedImage img = chunks.get(chunks.size() - 1);
            observer.imageAvaliable(this, img);
        }

        @Override
        protected Void doInBackground() throws Exception {
            try {
                while (keepRunning.get()) {
                    Instant anchor = Instant.now();
                    System.out.println("Snapshot");
                    BufferedImage image = bot.createScreenCapture(captureBounds);
                    System.out.println("Pubish");
                    publish(image);
                    Duration duration = Duration.between(anchor, Instant.now());
                    System.out.println("Took " + duration.toMillis());
                    duration = duration.minus(interval);
                    System.out.println("Time remaining " + duration.toMillis());
                    if (duration.isNegative()) {
                        long sleepTime = Math.abs(duration.toMillis());
                        System.out.println("Sleep for " + sleepTime);
                        Thread.sleep(sleepTime);
                    }
                }
            } catch (Exception exp) {
                exp.printStackTrace();
            }
            return null;
        }

    }
}

别忘了

您需要知道要捕获哪个屏幕。看看 startCapture 方法,这是我配置 CaptureWorker 的地方。我刚刚在索引 0 处抓取了屏幕,幸运地得到了我想要的屏幕

此外,这是不受支持的。这显示了问题的“如何捕获屏幕并将其渲染为 window”部分。您如何决定哪个屏幕或如何向用户显示该信息以进行配置完全取决于您