摘要:本文展示了如何使用Java的Robot类创建一个能够捕获主屏幕内容的屏幕捕捉程序。本程序处理捕捉屏幕外,还能够将整个屏幕或者屏幕的一部分保存成.jpg文件。
java.awt.Robot类真的很好玩。玩Robot会给你带来很多乐趣。下面让我们看看用Robot是怎么创建你自己的屏幕捕捉程序的。
程序界面
下图是我的屏幕捕捉程序的界面。你可以用它来捕捉屏幕,裁剪选取的图片内容,并将结果保存为.jpg文件。

屏幕捕捉程序的界面很简单,包括一个菜单和一个用于显示捕捉图像的滚动窗口。在窗口上按住鼠标左键拖动鼠标,你会看到一个红色的虚线矩形,这就是图像选取框,选取图片以后选择裁剪菜单,就可以对图像进行裁减。见下图:

系统实现
本程序包括3个源文件:
Capture.java:启动应用程序,构建GUI;
ImageArea.java:显示和选取屏幕图片的组件;
ImageFileFilter.java:文件选择过滤器,限制只能选择JPEG文件。
下面我将结合代码片断介绍如何实现屏幕捕捉程序。
屏幕捕获
要想使用Robot类创建屏幕捕捉程序,首先要创建Robot对象。在Capture类的
public static void main(String [] args)方法中用Robot的public Robot()创建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 。下面的代码片断展示了当菜单中捕捉菜单项按下后执行的屏幕捕捉动作。
 // 执行屏幕捕捉时隐藏屏幕捕捉程序,使其不显示在桌面上.
// 执行屏幕捕捉时隐藏屏幕捕捉程序,使其不显示在桌面上.

 setVisible (false);
setVisible (false);

 // 执行屏幕捕捉.
// 执行屏幕捕捉.

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

 // 完成屏幕捕捉后显示屏幕捕捉程序窗口.
// 完成屏幕捕捉后显示屏幕捕捉程序窗口.

 setVisible (true);
setVisible (true);

 // 用捕获的屏幕图片刷新ImageArea组件并相应调整滚动条.
// 用捕获的屏幕图片刷新ImageArea组件并相应调整滚动条.

 ia.setImage (biScreen);
ia.setImage (biScreen);

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

我不希望屏幕捕捉程序窗口遮住主屏幕,因此在捕捉屏幕之前我先将其隐藏。当获取到保存有屏幕图片象素的
java.awt.image.BufferedImage 后,屏幕捕捉程序窗口再次显现出来并通过ImageArea组件显示出
BufferedImage 。
图像选取
首先我们需要一个矩形框来标识我们想要裁剪的图像区域。在ImageArea组建中,提供了创建、操作、绘制这个矩形框的代码。下面的代码片断来自ImageArea.java,ImageArea类的构造函数创建了选取矩形框的实例,并且创建了
java.awt.BasicStroke 和
java.awt.GradientPaint 对象来定义选取框的外观,同时还注册了鼠标和鼠标移动监听,来帮助你操作矩形选取框。
 // 创建矩形选取框.最好是只创建一个选取框,
// 创建矩形选取框.最好是只创建一个选取框,
   // 这样比每次paintComponent()被调用时创建选取框好.
   // 减少了不必要的对象的创建

 rectSelection = new Rectangle ();
rectSelection = new Rectangle ();

 // 定义矩形选取框的轮廓.
// 定义矩形选取框的轮廓.

 bs = new BasicStroke (5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
bs = new BasicStroke (5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,

 0, new float []
                      0, new float []  { 12, 12 }, 0);
{ 12, 12 }, 0);

 // 定义矩形选取框的颜色
// 定义矩形选取框的颜色

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

 // 建立鼠标监听.
// 建立鼠标监听.

 MouseListener ml;
MouseListener ml;
 ml = new MouseAdapter ()
ml = new MouseAdapter ()

 
      {
{
 public void mousePressed (MouseEvent e)
         public void mousePressed (MouseEvent e)

 
          {
{
 // 没有捕获的图像时直接返回.
            // 没有捕获的图像时直接返回.

 if (image == null)
            if (image == null)
 return;
                return;

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

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

 // 建立鼠标移动监听.
// 建立鼠标移动监听.

 MouseMotionListener mml;
MouseMotionListener mml;
 mml = new MouseMotionAdapter ()
mml = new MouseMotionAdapter ()

 
       {
{
 public void mouseDragged (MouseEvent e)
          public void mouseDragged (MouseEvent e)

 
           {
{
 // 没有捕获的图像时直接返回.
             // 没有捕获的图像时直接返回.

 if (image == null)
             if (image == null)
 return;
                 return;

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

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

当点击鼠标时,鼠标事件处理器将
destx 和 
srcx都设置为鼠标的横坐标,纵坐标业一样。源坐标和目的坐标一样意味着删除当前的矩形选取框。我们可以通过调用repaint(),repaint()会调用public void paintComponent(Graphics g)实现删除矩形选取框。paintComponent()方法会比较srcx与destx, srcy与desty;如果他们不相等,就绘制一个新的矩形选取框。
 // 绘制矩形选取框.
// 绘制矩形选取框.

 if (srcx != destx || srcy != desty)
if (srcx != destx || srcy != desty)


 {
{
 // 计算左上和右下点的坐标.
    // 计算左上和右下点的坐标.

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

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

 // 确定矩形的原点.
    // 确定矩形的原点.

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

 // 确定矩形的长宽.
    // 确定矩形的长宽.

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

 // 绘制矩形.
    // 绘制矩形.

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

在绘制矩形之前,要先确定矩形的左上和右下角,以便算出矩形的原点和长宽。因此你可以任意拖动鼠标绘制选取框,最后
srcx/
destx 和
srcy/
desty中的最小值确定左上角,同理,最大值确定右下角。
图像裁剪
选取完图像以后,我们自然要裁剪它。当点击菜单中的“裁剪”菜单项时,ImageArea组件会将选中的图像裁减下来。如果裁剪成功,ImageArea的滚动条会重新设置,否则程序会弹出“超出范围”对话框。
 // 如果裁剪成功,重新设置滚动条.
// 如果裁剪成功,重新设置滚动条.

 if (ia.crop ())
if (ia.crop ())


 {
{
 jsp.getHorizontalScrollBar ().setValue (0);
    jsp.getHorizontalScrollBar ().setValue (0);
 jsp.getVerticalScrollBar ().setValue (0);
    jsp.getVerticalScrollBar ().setValue (0);
 }
}
 else
else
 showError ("超出范围");
    showError ("超出范围");也许你会问为什么会出现“超出范围”呢?请看下图

由于裁剪后GUI窗口不会改变大小,因此可能裁减后的图片比程序的GUI窗口小,GUI窗口的背景就显露出来。如果选取框包含了GUI窗口的背景象素,那么在裁剪时就会报“超出范围”错误。
裁剪功能是由ImageArea的
public boolean crop() 方法实现的。当裁剪成功或者没有可裁减图像时返回true,如果选取框包含有GUI窗口背景象素,就返回false。下面是代码:
 public boolean crop ()
public boolean crop ()


 {
{
 // 如果选取框只是一个点,返回true
   // 如果选取框只是一个点,返回true

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

 // 默认返回true.
   // 默认返回true.

 boolean succeeded = true;
   boolean succeeded = true;

 // 计算选取框的左上角和右下角坐标.
   // 计算选取框的左上角和右下角坐标.

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

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

 // 计算选取框的尺寸.
   // 计算选取框的尺寸.

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

 // 创建保存裁剪图像的图像缓冲.
   // 创建保存裁剪图像的图像缓冲.

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

 // 执行裁剪操作.
   // 执行裁剪操作.

 try
   try

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

 
    {
{
 succeeded = false;
      succeeded = false;
 }
   }

 g2d.dispose ();
   g2d.dispose ();

 if (succeeded)
   if (succeeded)
 setImage (biCrop);
       setImage (biCrop); 
 else
   else

 
    {
{
 // 准备删除选取框.
       // 准备删除选取框.

 srcx = destx;
       srcx = destx;
 srcy = desty;
       srcy = desty;

 // 删除选取框.
       // 删除选取框.

 repaint ();
       repaint ();
 }
   }

 return succeeded;
   return succeeded;
 }
}

会调用
BufferedImage的
public BufferedImage getSubimage(int x, int y, int w, int h)方法将选取框中的图像从原图像中裁剪下来。如果参数不是指定的BufferedImage区域,此方法会抛出java.awt.image.RasterFormatException 异常,返回fasle。
保存图像
本程序允许保存图像。你可以通过文件选择存对话框为要报存的图像取个名字。文件保存对话框在Capture类的构造函数中定义。
 final JFileChooser fcSave = new JFileChooser ();
final JFileChooser fcSave = new JFileChooser ();
 fcSave.setCurrentDirectory (new File (System.getProperty ("user.dir")));
fcSave.setCurrentDirectory (new File (System.getProperty ("user.dir")));
 fcSave.setAcceptAllFileFilterUsed (false);
fcSave.setAcceptAllFileFilterUsed (false);
 fcSave.setFileFilter (new ImageFileFilter ());
fcSave.setFileFilter (new ImageFileFilter ());

为了约束文件选择对话框所能保存只能保存JPEG文件,我们创建了一个ImageFileFilter类作为文件选择对话框的文件过滤器。如果传入方法public boolean accept (File f)的参数不是目录或者以.jpg .jpeg为后缀的文件,那么返回false
 public boolean accept (File f)
public boolean accept (File f)


 {
{
 // 允许用户选择文件夹.
   // 允许用户选择文件夹.

 if (f.isDirectory ())
   if (f.isDirectory ())
 return true;
       return true;

 // 允许用户选择以.jpg 或 .jpeg为后缀的文件
   // 允许用户选择以.jpg 或 .jpeg为后缀的文件

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

 if (i > 0 && i < s.length ()-1)
   if (i > 0 && i < s.length ()-1)

 
    {
{
 String ext = s.substring (i+1).toLowerCase ();
       String ext = s.substring (i+1).toLowerCase ();

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

 // 没有可以选择的.
   // 没有可以选择的.

 return false;
   return false;
 }
}

 // 显示文件选择器,不选中任何文件.
// 显示文件选择器,不选中任何文件.
 // 如果用户点击cancel,则退出.
// 如果用户点击cancel,则退出.

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

 // 获取选择的文件.如果不是以.jpg 或.jpeg为后缀,
// 获取选择的文件.如果不是以.jpg 或.jpeg为后缀,
 // 添加.jpg后缀

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

 // 如果文件已存在,通知用户
// 如果文件已存在,通知用户 
                  
 if (file.exists ())
if (file.exists ())


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

如果文件不存在,或者你允许覆盖已有文件,程序将会保存图片。为了完成保存,我们使用了Java的ImageIO框架。代码如下:
 ImageWriter writer = null;
ImageWriter writer = null;
 ImageOutputStream ios = null;
ImageOutputStream ios = null;

 try
try


 {
{
 // 获得一个jpeg 类型的写入器
    // 获得一个jpeg 类型的写入器

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

 // 验证写入器是否存在
    // 验证写入器是否存在

 if (!iter.hasNext ())
    if (!iter.hasNext ())

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

 writer = (ImageWriter) iter.next();
    writer = (ImageWriter) iter.next();


 // 获取写入器写入目标
    // 获取写入器写入目标

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

 // 设置jpeg压缩率为 95%.
    // 设置jpeg压缩率为 95%.

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

 // 写入图像.
    // 写入图像.

 writer.write (null,
    writer.write (null,
 new IIOImage ((BufferedImage)
                  new IIOImage ((BufferedImage)
 ia.getImage (), null, null),
                                ia.getImage (), null, null),
 iwp);
                  iwp);
 }
}
 catch (IOException e2)
catch (IOException e2)


 {
{
 showError (e2.getMessage ());
    showError (e2.getMessage ());
 }
}
 finally
finally


 {
{
 try
    try

 
     {
{
 // 清理.
        // 清理.

 if (ios != null)
        if (ios != null)

 
         {
{
 ios.flush ();
            ios.flush ();
 ios.close ();
            ios.close ();
 }
        }

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

 
     {
{
 }
    }
 }
}

保存后的清理工作很有必要。我将清理代码上到finally块中,这样不管是正常的保存成功还是意外地中止,都能执行响应的清理工作。
改进
本文中的这个屏幕捕捉程序只能捕捉主屏幕设备的图像。也许你希望捕获所有屏幕的图像。要实现这个功能,你可以将下面的代码加入到Capture.java中:
 GraphicsEnvironment graphenv = GraphicsEnvironment.getLocalGraphicsEnvironment ();
GraphicsEnvironment graphenv = GraphicsEnvironment.getLocalGraphicsEnvironment ();
 GraphicsDevice [] screens = graphenv.getScreenDevices ();
GraphicsDevice [] screens = graphenv.getScreenDevices ();
 BufferedImage [] captures = new BufferedImage [screens.length];
BufferedImage [] captures = new BufferedImage [screens.length];

 for (int i = 0; i < screens.length; i++)
for (int i = 0; i < screens.length; i++)


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

上面介绍了用Robot类制作Java屏幕捕捉程序的全过程。希望对大家能有所启示。如果需要本程序的全部源代码,请留下邮箱,我会及时发给你。
	
posted on 2006-05-17 11:30 
学二的猫 阅读(5578) 
评论(72)  编辑  收藏  所属分类: 
Java禅机