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

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

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

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

</td> </tr> <tr> <td height="35" valign="top" class="ArticleTeitle"> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td height="48" align="center" valign="top"> </td> </tr> <tr> <td height="20"> 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观点介绍完这个应用之后,我将解释实现的关键部分。

作者: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

应用程序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技术领域。

</td> </tr> </table> </td> </tr> <tr>


↑返回目录
前一篇: String VS StringBuffer 让你的程序运行得更快
后一篇: java实现的LZW 压缩算法源码