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

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

mydeman 发表于 2006-09-15 09:46:15
作者:mydeman;Jeff Friesen     来源:matrix;javaworld
摘要:
Java Fun and Games(Java娱乐和游戏)提供了通过Java的Robot类捕获主屏幕设备的功能,并且可以将整个屏幕或者选定的一部分保存为jpeg文件。这篇文章以Swing应用的形式实现了屏幕捕获工具。

摘要

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。

image
图 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显示了选择图像的一部分时还可能选中背景的一部分。

image
图 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 
 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值