Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 318|回复: 0

[Java基础知识]编写一个基于Java Robot类的屏幕捕获工具

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-2 05:13:37 | 显示全部楼层 |阅读模式
    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);[/code]
       
           你不希望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);[/code]
       
           当按下鼠标时,鼠标事件处理器对相同的横向鼠标坐标设置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);
    }[/code]
       
          在选择矩形绘制以前,它的左上和右下角必须对标示出来,用来确定矩形的原点和范围。以至于你可以在不同的方向拖拽出选择矩形(例如右下或者左上方向),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.");[/code]
       
          因为修剪操作不重置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;
    }[/code]
       
           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 ());[/code]
       
          为了限制文件选择器的选择是文件夹或者是以.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;
    }[/code]
       
           当你选择了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;
    }[/code]
       
          如果文件不存在或者你允许覆盖已经存在的文件,监听器就会将捕获的内容保存为一个选择的文件。为了完成这个任务,监听器使用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)
        {
        }
    }[/code]
       

       
    让代码自己清理一直是一个不错的主意。我把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 .getDisplayMode ();
        Rectangle bounds = new Rectangle (0, 0, mode.getWidth (), mode.getHeight ());
        captures = new Robot (screens ).createScreenCapture (bounds);
    }[/code]
       
           把以上代码放到Capture菜单项的动作监听器内。然后先引入代码创建一个bigScreen要引用的足够大的BufferedImage,它可以保存被captures数组引用的所有BufferedImage内容;接着引入代码把它们的绘制到bigScreen中。Capture现在成为了多屏幕捕获器就好像是一个单屏幕捕获器。

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

             
            

             
            
          
         
       

      
         
      


    源码下载:http://file.javaxxz.com/2014/10/2/051337172.zip
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-27 02:07 , Processed in 0.321064 second(s), 36 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表