Java不应该这么慢(抓屏)

想要抓屏幕的一部分,并且保存起来用于网络传送,一般如下:

try
{
    int width = 200;

    int height = 300;

    java.awt.Rectangle rectangle = new java.awt.Rectangle( 0, 0, width, height );

    java.awt.Robot robot = new java.awt.Robot();

    java.awt.image.BufferedImage image = robot.createScreenCapture( rectangle );

    int[] rgbs = image.getRGB( 0, 0, width, height, null/*int[]*/, 0, width );
}
catch( Exception e ){ e.printStackTrace(); }

看Robot源代码发现每次都要新建BufferedImage,效率极低,搜索Robot中createScreenCapture方法,发现关键的一行:

pixels/*int[]*/ = peer/*?*/.getRGBPixels( screenRect/*Rectangle*/ );

那么peer从哪来?继续搜索找到:

Toolkit toolkit = Toolkit.getDefaultToolkit();

if( toolkit instanceof ComponentFactory/*?*/ ){ peer/*RobotPeer*/ = ( (ComponentFactory)toolkit ).createRobot( this, screen/*?*/ ); }

其中,ComponentFactory是SUN未公开的方法(见附注),是个接口(sun.awt.ComponentFactory),

peer是RobotPeer,也是个接口(java.awt.peer.RobotPeer),而screen如下:

GraphicsDevice screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

这样,我们就可以这样写来得到RGB的数组:

int width = 200;

int height = 300;

Rectangle rectangle = new Rectangle( 0, 0, width, height );

GraphicsDevice screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

Toolkit toolkit = Toolkit.getDefaultToolkit();

if( toolkit instanceof ComponentFactory )
{
    RobotPeer peer = (ComponentFactory)toolkit.createRobot/*?*/( null/*?new Robot()*/, screen );

    int[] rgbs = peer.getRGBPixels( rectangle );
}

至此,我们已经得到一个比较满意的解决方案了,但是,通过RobotPeer这个接口我们可以知道,

真正的Peer被SUN隐藏在未公开的类中,所以我们在sun.awt包中搜索createRobot字符串,

找到sun//awt//HeadlessToolkit.java,sun//awt//SunToolkit.java,sun//awt//windows//WToolkit.java这三个Toolkit类,

其中只有awt/windows/WToolkit.java真正实现了createRobot方法,如下:

return new WRobotPeer( graphicsdevice/*GraphicsDevice*/ );  //没有使用传进来的Robot参数,所以createRobot中参数Robot可以为null

也就是说,在Windows平台下,Robot的实现是sun//awt//windows//WRobotPeer.java,

而Toolkit的实现是sun//awt//windows//WToolkit.java(这个现在暂且不谈),

来看看WRobotPeer.java中是如何抓取屏幕得到RGB数组的吧,源代码如下:

public int[] getRGBPixels( Rectangle rectangle )
{
    rectangle.translate( offset.x, offset.y );

    int ai[] = new int[rectangle.width * rectangle.height];

    getRGBPixels( rectangle.x, rectangle.y, rectangle.width, rectangle.height, ai );

    return ai;
}

private native void getRGBPixels( int i, int j, int k, int l, int ai[] );

显然它最终是调用本地方法来实现的,不过它竟然每次都重建int数组(想想1024 X 768有多大),这个效率可是够“好”的。

我们需要的仅仅是getRGBPixels( int i, int j, int k, int l, int ai[] )这个私有的本地方法。

int width = 200;

int height = 300;

Rectangle rectangle = new Rectangle( 0, 0, width, height );

int[] rgbs = new int[rectangle.width * rectangle.height];  //这步必须做,否则会让JVM崩溃

GraphicsDevice screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

Toolkit toolkit = Toolkit.getDefaultToolkit();

RobotPeer peer = ( toolkit instanceof ComponentFactory ? ( (ComponentFactory)toolkit ).createRobot( null/*new Robot()*/, screen ) : null );

if( peer == null ){ System.err.println( "Could not get RobotPeer." ); return; }

Method method = peer.getClass().getDeclaredMethod( "getRGBPixels", int.class, int.class, int.class, int.class, int[].class );

method.setAccessible( true );

method.invoke( peer, rectangle.x, rectangle.y, rectangle.width, rectangle.height, rgbs );

结论:

其实如果SUN在RobotPeer中向外提供了getRGBPixels( int i, int j, int k, int l, int ai[] )这个方法,那么也就不会有那么多烦人的性能问题了。

附注:

怎样得到SUN未公开的类的源文件?

用反编译工具jad.exe,我用的是1.7版本的。

1. 进入X://JDK1.4.2//jre//lib//,找到rt.jar文件,用压缩软件winrar.exe打开并且解压,我们假定解压生成的目录是rt。

2. 进入X://JDK1.4.2//jre//lib//rt//,将jad.exe复制到此目录下,运行如下命令:

   JAD.EXE -o -r -s java -d decompiled_src ./**/*.class

3. 运行完毕后(可能会看到失败的提示,不管它),会生成decompiled_src文件夹,此时,你就可以从里面找需要的类文件了,当然,有些类文件还是参照官方的为好。

测试:

如下是我用来测试三种不同实现方法性能的小例子:

import sun.awt.ComponentFactory;

import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Rectangle;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;

import java.awt.peer.RobotPeer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;


class TestSnapshot
{
    private Rectangle rectangle;

    private int[] rgbs;

    public TestSnapshot() throws Exception
    {
        this.rectangle = new Rectangle( 0, 0, 200, 100 );

        this.rgbs = new int[rectangle.width * rectangle.height];

        long start = 0L;

        int times = 10000;

       
        {
            start = System.currentTimeMillis();

            GraphicsDevice screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

            Toolkit toolkit = Toolkit.getDefaultToolkit();

            RobotPeer peer = ( toolkit instanceof ComponentFactory ? ( (ComponentFactory)toolkit ).createRobot( null/*new Robot()*/, screen ) : null );

            if( peer == null ){ System.err.println( "Could not get RobotPeer." ); return; }

           

            for( int n = 0; n < times; n ++ ){ rgbs = peer.getRGBPixels( rectangle ); }

            System.err.println( System.currentTimeMillis() - start );
        }
       
        {
            start = System.currentTimeMillis();

            Robot robot = new Robot();

            Field peerField = robot.getClass().getDeclaredField( "peer" );

            peerField.setAccessible( true );

            Object robotPeer = peerField.get( robot );

            Method method = robotPeer.getClass().getDeclaredMethod( "getRGBPixels", int.class, int.class, int.class, int.class, int[].class );

            method.setAccessible( true );

           

            for( int n = 0; n < times; n ++ ){ method.invoke( robotPeer, rectangle.x, rectangle.y, rectangle.width, rectangle.height, rgbs ); }

            System.err.println( System.currentTimeMillis() - start );
        }
       
        {
            start = System.currentTimeMillis();

            GraphicsDevice screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

            Toolkit toolkit = Toolkit.getDefaultToolkit();

            RobotPeer peer = ( toolkit instanceof ComponentFactory ? ( (ComponentFactory)toolkit ).createRobot( null/*new Robot()*/, screen ) : null );

            if( peer == null ){ System.err.println( "Could not get RobotPeer." ); return; }

            Method method = peer.getClass().getDeclaredMethod( "getRGBPixels", int.class, int.class, int.class, int.class, int[].class );

            method.setAccessible( true );

           

            for( int n = 0; n < times; n ++ ){ method.invoke( peer, rectangle.x, rectangle.y, rectangle.width, rectangle.height, rgbs ); }

            System.err.println( System.currentTimeMillis() - start );
        }
    }

    public static void main( String[] args ) throws Exception { new TestSnapshot(); }
}

参考文章:

http://dahai686123.blog.163.com/blog/static/286670492008331029413/

http://forums.sun.com/thread.jspa?threadID=5283919

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值