站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Java 专题捕获屏幕-编写一个基于Java Robot类的屏幕捕获工具

捕获屏幕-编写一个基于Java Robot类的屏幕捕获工具

摘要: 捕获屏幕-编写一个基于Java Robot类的屏幕捕获工具
内容: 摘要

Java Fun and Games(Java娱乐和游戏)提供了通过Java的Robot类捕获主屏幕设备的功能,并且可以将整个屏幕或者选定的一部分保存为jpeg文件。

注意:现在你可以使用在线开发工具DevSquare编译和运行Java Fun and Games中提供的applet。DevSquare入门请阅读资源中提供的用户向导。

java.awt.Robot类为娱乐功能提供了一些有用的方法。其中一个包括了建立屏幕捕获工具的功能。Java Fun and Games给出了一个使用Robot捕获主屏幕设备内容的工具。

这一部分从我以前的几部分中分离出来了,因为它并不是集中在applet实现上。这篇文章以Swing应用的形式实现了屏幕捕获工具。从GUI观点介绍完这个应用之后,我将解释实现的关键部分。

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Jeff Friesen;mydeman
原文:http://www.javaworld.com/javaworld/jw-04-2006/jw-0424-funandgames.html
Matrix:http://www.matrix.org.cn/resource/article/2006-09-15/Java+Robot_f9598e5e-445b-11db-af0b-0f766c077b58.html
关键字:Java Robot;捕获屏幕

应用程序GUI
我的Capture程序提供了一个图形用户界面(GUI,Graphic User Interface),通过它你可以选择捕获图像的一部分,修剪图像到选择内容,以及将结果图像保存为jpeg文件。图1显示了包含一个捕获示例的Capture的GUI。


图 1. 红白相间的虚线所形成的矩形表示了当前选中的区域

Capture的GUI由菜单栏和显示捕获图像的可滚动窗口组成。如图1所示,选择矩形(通过拖拽鼠标)表示了捕获图形的一个矩形区域。

菜单栏提供了File和Capture菜单:
---File提供Save As…(另存为)和Exit(退出)菜单项,可以通过文件选择器保存当前捕获为一个jpeg文件,和退出Capture。尽管你可以直接选择这些菜单项,但是你会发现使用它们的快捷键Alt-S和Alt-X会更加方便。
---Capture提供Capture(捕获)和Crop(修剪)菜单项,可以捕获当前主屏幕设备的内容和修剪一个图像为选择矩形的内容。和File菜单项一样,这些菜单项也有它们自己的方便的快捷键:Capture(Alt-C)和Crop(Alt-K)。


应用实现

有三个源文件来描述Capture的GUI:Capture.java(启动应用程序和构造GUI)、ImageArea.java( 描述了一个用来显示捕获的内容的组件,你也可以在其中选择捕获的一部分或修剪捕获的内容)和ImageFileFilter.java(限制文件选择器的选择是文件夹和jpeg文件)。在这一部分下面,我从这些源文件中摘录了一些代码片断来说明Capture的工作过程。

机器人屏幕捕获
为了使用Robot类捕获屏幕,Capture必须先创建一个Robot对象。Capture类的public static void main(String [] args)方法尝试调用Robot的public Robot()构造函数来创建这个对象。如果创建成功,就会返回一个针对主屏幕设备坐标系的Robot引用。如果平台不支持低级控制(在没有屏幕设备的环境这是成立的),将会抛出java.awt.AWTException。如果平台不允许创建Robot对象就会抛出java.lang.SecurityException。但愿你不会再遇到其他异常。

假设Robot对象已被创建,main()调用Capture类的构造函数创建一个GUI。作为GUI创建的一部分,Capture通过调用dimScreenSize = Toolkit.getDefaultToolkit().getScreenSize();获得主屏幕设备的尺寸。因为用来显示屏幕捕获的内容的Robot的public BufferedImage createScreenCapture(Rectangle screenRect)方法,需要一个java.awt.Rectangle参数,所以构造函数通过rectScreenSize = new Rectangle(dimScreenSize);将java.awt.Dimension对象转换为一个Rectangle对象。当Capture菜单项的动作监听器被调用时,下面摘录的Capture.java片断就会调用createScreenCapture()。
// Hide Capture's main window so that it does not appear in
// the screen capture.

setVisible (false);

// Perform the screen capture.

BufferedImage biScreen;
biScreen = robot.createScreenCapture (rectScreenSize);

// Show Capture's main window for continued user interaction.

setVisible (true);

// Update ImageArea component with the new image and adjust
// the scrollbars.

ia.setImage (biScreen);

jsp.getHorizontalScrollBar ().setValue (0);
jsp.getVerticalScrollBar ().setValue (0);


你不希望Capture的GUI遮住你想要捕获的任何内容。这就是为什么代码中隐藏Capture GUI优先级高于完成捕获。在获取了包含屏幕像素copy的java.awt.image.BufferedImage后,代码片断显示出GUI,并且通过图像区域组件显示出BufferedImage的内容。

子图像选择
当从一个捕获的图像中获取子图像时需要一个选择矩形。ImageArea类提供代码来创建、操作和绘制选择矩形。如下面摘录的ImageArea.java所示,这个类的构造函数以一个Rectangle实例创建选择矩形,创建java.awt.BasicStoke和java.awt.GradientPaint对象定义了矩形的轮廓外观(保持它与底层图像分离),注册鼠标和鼠标动作监听器让你能够操作选择矩形。
// Create a selection Rectangle. It's better to create one Rectangle
// here than a Rectangle each time paintComponent() is called, to reduce
// unnecessary object creation.

rectSelection = new Rectangle ();

// Define the stroke for drawing selection rectangle outline.

bs = new BasicStroke (5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
0, new float [] { 12, 12 }, 0);

// Define the gradient paint for coloring selection rectangle outline.

gp = new GradientPaint (0.0f, 0.0f, Color.red, 1.0f, 1.0f, Color.white,
true);

// Install a mouse listener that sets things up for a selection drag.

MouseListener ml;
ml = new MouseAdapter ()
{
public void mousePressed (MouseEvent e)
{
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a sub-image.
// This is the reason for the if (image == null) test.

if (image == null)
return;

destx = srcx = e.getX ();
desty = srcy = e.getY ();

repaint ();
}
};
addMouseListener (ml);

// Install a mouse motion listener to update the selection rectangle
// during drag operations.

MouseMotionListener mml;
mml = new MouseMotionAdapter ()
{
public void mouseDragged (MouseEvent e)
{
// When you start Capture, there is no captured image.
// Therefore, it makes no sense to try and select a
// sub-image. This is the reason for the if (image == null)
// test.

if (image == null)
return;

destx = e.getX ();
desty = e.getY ();

repaint ();
}
};
addMouseMotionListener (mml);


当按下鼠标时,鼠标事件处理器对相同的横向鼠标坐标设置destx和srcx,对于纵向鼠标坐标亦是如此。源变量和目标变量同样表示哪些显示的选择矩形应该被移除了。它通过调用repaint(),导致public void paintComponent(Graphics g)被调用。这个方法将srcx和srcy分别与destx和desty相比较,如果他们不同,就绘制一个选择矩形:
// Draw the selection rectangle if present.

if (srcx != destx || srcy != desty)
{
// Compute upper-left and lower-right coordinates for selection
// rectangle corners.

int x1 = (srcx < destx) ? srcx : destx;
int y1 = (srcy < desty) ? srcy : desty;

int x2 = (srcx > destx) ? srcx : destx;
int y2 = (srcy > desty) ? srcy : desty;

// Establish selection rectangle origin.

rectSelection.x = x1;
rectSelection.y = y1;

// Establish selection rectangle extents.

rectSelection.width = (x2-x1)+1;
rectSelection.height = (y2-y1)+1;

// Draw selection rectangle.

Graphics2D g2d = (Graphics2D) g;
g2d.setStroke (bs);
g2d.setPaint (gp);
g2d.draw (rectSelection);
}


在选择矩形绘制以前,它的左上和右下角必须对标示出来,用来确定矩形的原点和范围。以至于你可以在不同的方向拖拽出选择矩形(例如右下或者左上方向),srcx/destx和srcy/desty的最小值表示左上角,相似地,它们的最大值表示右下角。

图像修剪
在选择子图像后,你想要修剪捕获的图像得到子图像。图像修剪启动Crop中的菜单项的动作监听器,它请求ImageArea将捕获的图像修剪为选择的子图像。若操作成果,监听器则重置ImageArea的滚动条。反之,监听器通过对话框给出一个“Out of bounds”错误信息。
// Crop ImageArea component and adjust the scrollbars if
// cropping succeeds.

if (ia.crop ())
{
jsp.getHorizontalScrollBar ().setValue (0);
jsp.getVerticalScrollBar ().setValue (0);
}
else
showError ("Out of bounds.");


因为修剪操作不重置Capture GUI的大小,所以可以同时看到主窗口的背景和结果图像(初始修剪后的)。图2显示了选择图像的一部分时还可能选中背景的一部分。


图 2. 尝试选择多于这个图像

主窗口的背景像素不是捕获的图像的一部分;就不可能把它们包含在修剪的图片内。因此,无论何时把背景像素包含在修剪区域内,操作都会失败,并且会给出一个“Out of bounds”错误信息。

修剪操作由ImageArea的public Boolean crop()方法处理。如果完成了修剪或者没有选择子图像(当没有选中内容时调用这个方法是非常方便的)该方法(如下所示)返回true。如果在选择区域中包含了背景像素则返回false。
public boolean crop ()
{
// There is nothing to crop if the selection rectangle is only a single
// point.

if (srcx == destx && srcy == desty)
return true;

// Assume success.

boolean succeeded = true;

// Compute upper-left and lower-right coordinates for selection rectangle
// corners.

int x1 = (srcx < destx) ? srcx : destx;
int y1 = (srcy < desty) ? srcy : desty;

int x2 = (srcx > destx) ? srcx : destx;
int y2 = (srcy > desty) ? srcy : desty;

// Compute width and height of selection rectangle.

int width = (x2-x1)+1;
int height = (y2-y1)+1;

// Create a buffer to hold cropped image.

BufferedImage biCrop = new BufferedImage (width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = biCrop.createGraphics ();

// Perform the crop operation.

try
{
BufferedImage bi = (BufferedImage) image;
BufferedImage bi2 = bi.getSubimage (x1, y1, width, height);
g2d.drawImage (bi2, null, 0, 0);
}
catch (RasterFormatException e)
{
succeeded = false;
}

g2d.dispose ();

if (succeeded)
setImage (biCrop); // Implicitly remove selection rectangle.
else
{
// Prepare to remove selection rectangle.

srcx = destx;
srcy = desty;

// Explicitly remove selection rectangle.

repaint ();
}

return succeeded;
}


crop()方法调用BufferedImage的public BufferedImage getSubimage(int x, int y, int w, int h)方法摘取选择区域内的子图像。如果该方法的参数没有指定BufferedImage内的图像,它就会抛出一个java.awt.image.RasterFormatException,因此就会返回false。

图像保存
Capture允许你把捕获的图像保存为一个jpeg文件。你通过一个保存文件选择器指定文件名,选择器由Capture类的构造函数创建:
// Construct a save file-chooser. Initialize the starting directory to
// the current directory, do not allow the user to select the "all files"
// filter, and restrict the files that can be selected to those ending
// with .jpg or .jpeg extensions.

final JFileChooser fcSave = new JFileChooser ();
fcSave.setCurrentDirectory (new File (System.getProperty ("user.dir")));
fcSave.setAcceptAllFileFilterUsed (false);
fcSave.setFileFilter (new ImageFileFilter ());


为了限制文件选择器的选择是文件夹或者是以.jpg或.jpeg为后缀的文件,就使用了ImageFileFilter类的一个实例作为保存时文件选择器的文件过滤器。该方法对于任何非文件夹和后缀名非.jpg/.jpeg的文件都返回false:
public boolean accept (File f)
{
// Allow the user to select directories so that the user can navigate the
// file system.

if (f.isDirectory ())
return true;

// Allow the user to select files ending with a .jpg or a .jpeg
// extension.

String s = f.getName ();
int i = s.lastIndexOf ('.');

if (i > 0 && i < s.length ()-1)
{
String ext = s.substring (i+1).toLowerCase ();

if (ext.equals ("jpg") || ext.equals ("jpeg"))
return true;
}

// Nothing else can be selected.

return false;
}


当你选择了Save As…菜单项时,它的监听器就会显示一个保存文件选择器。假定你没有退出选择器,监听器就会确保你选择的文件名是以.jpg或.jpeg为后缀名。继续,监听器会确定文件是否存在,这样你就不会无意中覆盖一个存在的文件。
// Present the "save" file-chooser without any file selected.
// If the user cancels this file-chooser, exit this method.

fcSave.setSelectedFile (null);
if (fcSave.showSaveDialog (Capture.this) !=
JFileChooser.APPROVE_OPTION)
return;

// Obtain the selected file. Validate its extension, which
// must be .jpg or .jpeg. If extension not present, append
// .jpg extension.

File file = fcSave.getSelectedFile ();
String path = file.getAbsolutePath ().toLowerCase ();
if (!path.endsWith (".jpg") && !path.endsWith (".jpeg"))
file = new File (path += ".jpg");

// If the file exists, inform the user, who might not want
// to accidentally overwrite an existing file. Exit method
// if the user specifies that it is not okay to overwrite
// the file.

if (file.exists ())
{
int choice = JOptionPane.
showConfirmDialog (null,
"Overwrite file?",
"Capture",
JOptionPane.
YES_NO_OPTION);
if (choice == JOptionPane.NO_OPTION)
return;
}


如果文件不存在或者你允许覆盖已经存在的文件,监听器就会将捕获的内容保存为一个选择的文件。为了完成这个任务,监听器使用Java的ImageIO框架选择一个jpeg writer,指定文件作为writer的目标,设置writer的压缩品质为95%,然后把图像写入到文件中。
ImageWriter writer = null;
ImageOutputStream ios = null;

try
{
// Obtain a writer based on the jpeg format.

Iterator iter;
iter = ImageIO.getImageWritersByFormatName ("jpeg");

// Validate existence of writer.

if (!iter.hasNext ())
{
showError ("Unable to save image to jpeg file type.");
return;
}

// Extract writer.

writer = (ImageWriter) iter.next();


// Configure writer output destination.

ios = ImageIO.createImageOutputStream (file);
writer.setOutput (ios);

// Set jpeg compression quality to 95%.

ImageWriteParam iwp = writer.getDefaultWriteParam ();
iwp.setCompressionMode (ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality (0.95f);

// Write the image.

writer.write (null,
new IIOImage ((BufferedImage)
ia.getImage (), null, null),
iwp);
}
catch (IOException e2)
{
showError (e2.getMessage ());
}
finally
{
try
{
// Cleanup.

if (ios != null)
{
ios.flush ();
ios.close ();
}

if (writer != null)
writer.dispose ();
}
catch (IOException e2)
{
}
}


让代码自己清理一直是一个不错的主意。我把ImageIO的清理代码放在了finally子句中,以至于无论是正常结束还是抛出异常,它都可以执行。

总结

Capture限制了捕获的内容只能在主屏幕设备内。你可能想增强Capture来捕获所有附加屏幕设备(或许是一个巨大的虚拟屏幕)的内容。增强之一,你需要包含下面的代码,它捕获所有屏幕的内容,将它和Capture.java已经存在的代码集成。
GraphicsEnvironment graphenv = GraphicsEnvironment.getLocalGraphicsEnvironment ();
GraphicsDevice [] screens = graphenv.getScreenDevices ();
BufferedImage [] captures = new BufferedImage [screens.length];

for (int i = 0; i < screens.length; i++)
{
DisplayMode mode = screens [i].getDisplayMode ();
Rectangle bounds = new Rectangle (0, 0, mode.getWidth (), mode.getHeight ());
captures [i] = new Robot (screens [i]).createScreenCapture (bounds);
}


把以上代码放到Capture菜单项的动作监听器内。然后先引入代码创建一个bigScreen要引用的足够大的BufferedImage,它可以保存被captures数组引用的所有BufferedImage内容;接着引入代码把它们的绘制到bigScreen中。Capture现在成为了多屏幕捕获器就好像是一个单屏幕捕获器。

关于作者
Jeff Friesen是一个自由软件开发者和教育家,特别是在C、C++和Java技术领域。

资源
Matrix中文Java社区:http://www.matrix.org.cn
下载文中的代码文件:http://www.javaworld.com/javaworld/jw-04-2006/games/jw-0424-funandgames.zip
你可以使用在线开发工具DevSquare编译和运行Java Fun And Games中提供的Applet。工具入门请阅读这篇用户向导:
http://www.javaworld.com/javaworld/jw-12-2005/jw-devsquare.html
DevSquare:http://www.devsquare.com/index.html

Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd 摘要

Java Fun and Games(Java娱乐和游戏)提供了通过Java的Robot类捕获主屏幕设备的功能,并且可以将整个屏幕或者选定的一部分保存为jpeg文件。

注意:现在你可以使用在线开发工具DevSquare编译和运行Java Fun and Games中提供的applet。DevSquare入门请阅
↑返回目录
前一篇: 通过Web scripting使用自己的程序语言
后一篇: 使用WebRowSet完成JDBC的大部分任务