Conceptually, the idea is pretty simple. You want to use an instance of Robot
to capture a snapshot of the screen.
The first thing you need to do though, is get the "area" of the screen you want to capture and this is not as easy it as might sound as Java doesn't seem to provide the "screen names" (on Windows GraphicsDevice#getIDstring
might return the name, but on MacOS it didn't)
So, I started by trying to look at the screen resolutions and tried to figure out which screen I wanted...
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice lstGDs[] = ge.getScreenDevices();
for (GraphicsDevice gd : lstGDs) {
System.out.println(gd.getDisplayMode());
}
I could have also used getBounds
and tried to figure out where each screen was positioned, but what ever works.
Once you have this information, you can use GraphicsDevice#getBounds
to get the physical area of the screen, which you can feed to Robot
Now, you need some way to produce and consume those snapshots. You need to be aware of the fact that Swing is not thread safe and is single threaded. This means you can't capture the snapshot from the Event Dispatching Thread and should not try to update the UI from outside of the Event Dispatching Thread. See Concurrency in Swing for more details.
With this in mind, I decided to go with a SwingWorker
. It does most of the heavy lifting for us and provides a simplified workflow. See Worker Threads and SwingWorker for more details.
Okay, the next issue to solve, is how to render the screen. The basic concept is just to paint the image onto a component, which isn't that hard, the hard part is scaling the image.
There's been a lot of discussion on the subject, but you could take a look at How do I resize images inside an application when the application window is resized? and Java: maintaining aspect ratio of JPanel background image
The first presents a concept of "scale to fit" and "scale to fill" algorithms and the second presents a better way to scale a image then using Image#getScaledInstance
(which the example uses) as getScaledInstance
doesn't always present the best result. You could also take a look at Quality of Image after resize very low -- Java which presents some more ideas and discussions.

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;
}
}
}
Don't forget
You need to know which screen you want to capture. Take a look at the startCapture
method, this where I configured the CaptureWorker
. I just grabbed the screen at index 0
and was lucky enough to get the screen I wanted
Also, this is unsupported. This presents the "how to capture a screen and renderer it a window" portion of your question. How you decide which screen or how you might present that information to the user to configure is all up to you