我正在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;
}
}
}
}
任何帮助表示赞赏!
答案 0 :(得分:6)
getGraphics
,这不是自定义绘画的方式。 run
方法可能只是快速运行,请考虑使用javax.swing.Timer
代替Toolkit.getDefaultToolkit().getScreenSize()
仅返回“默认”屏幕,不考虑拆分屏幕Thread
,其作用是捕获桌面的给定部分。您还可以使用SwingWorkers
实现此目的,这样可以更轻松地将更新同步回UI ... 看看:
更新了示例
这只是一个概念证明!你应该了解它正在尝试做什么,并借鉴它......
您可以在预览窗口内看到预览窗口更改...有点怪...
它在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,可能有助于减少一些开销......不,我没有代码可以做到这一点;)