java屏幕取词的实现

来源:百度文库 编辑:神马文学网 时间:2024/04/27 21:08:03
java屏幕取词的实现文章来源:本站收集    点击数: 492    更新时间:2007-7-7 17:18:09     相信很多人都用过金山词霸。将鼠标放在任何应用程序的任何组件的文字上,它能将这个文字取出翻译成中文。单就辞典翻译来说并没有什么难点,但是屏幕取词功能的确是一个神奇的功能                                                                                   
     我很早以前就在猜测金山词霸是怎么实现这种功能的。开始我以为是金山词霸将鼠标下的文字图像剪裁出,然后使用图像模式识别出单词。但后来发现这个想法不太现实,不说图像识别的难度了,这样识别单词也会给系统带来巨大的开销。后来偶然发现金山词霸是取不到图像上的文字的,才认识到不是图像模式识别技术。再后来问过一个师兄,这个师兄说,这是因为金山词霸在系统中做了钩子,当应用程序使用系统提供的文字渲染类库时,渲染的文字就会通过回调钩子传给金山词霸,因此就获得了这个文本。于是才恍然大悟。   
     也就说如果某个应用程序恰好不是调用操作系统的文本显示类库,那么金山词霸也是没有办法取到该应用程序上的文字。

      这个想法很快就在很多程序上得到验证。如firefox,它使用自己的渲染引擎。再如Adobe AcrobatReader,它渲染文本也是使用自己的字体渲染技术(后来金山词霸专门为Adobe AcrobatReader做了个插件)。再如OpenOffice,它的界面是使用Swing来渲染的,因此也没有办法取词。

   OpenOffice这个例子引出了这样一个事实,所有的Swing应用程序都是无法使用金山词霸取词的。

    偶然一次在JavaLobby看贴,看到有人问Java 6的Java 2D类库是不是做了很大改动,因为他们做的Swing界面取词功能失效了。详细阅读才得知,他们是在Swing底层Java 2D的文本渲染管道添加侦听接口,也就是类似于金山词霸的钩子。但由于他们使用了Java 2D的内部实现接口,结果在Java升级时,他们的程序就无法正常工作了。   
     我一直对这个想法很感兴趣。因此今天除了学习及翻译Java 2D API的文档外,还自己实验做了个Swing屏幕取词实验程序。当然这个辞典重点不是翻译,它只是简单的凑了几个中英文的单词对照。这个实验非常粗化,但足以证明完全可以不通过Java 2D API的内部接口实现屏幕取词。如果细化做下去,将这个类库作为JRE的扩展安装上去,应该能实现在所有Swing应用上的取词。    
     这个取词功能的核心思想是,监听整个swing事件系统的鼠标移动事件处理器(通过Toolkit.getDefaultToolkit().addAWTEventListener实现),为所有窗口及其组件添加鼠标移动侦听事件处理器。这些处理器在初始化时根据当前组件封装一个代理Graphics:

public class ProxyGraphics extends Graphics2D {

    private Graphics2D proxy;
    private ArrayList texts = new ArrayList();

    /** Creates a new instance of ProxyGraphics */
    public ProxyGraphics(Graphics2D g2d) {
        proxy = g2d;
    }

    ......

    public void draw(Shape s) {
        proxy.draw(s);
    }

    public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
        return proxy.drawImage(img, xform, obs);
    }

    ......

    public void drawString(String str, int x, int y) {

        //在这儿把要渲染的文字记录下来,当然也记录下其位置和边界

        ......

        ......
        proxy.drawString(str, x, y);
    }
    ...

 }

    留意这个ProxyGraphics,它实际上把所有的工作都交给了一个代理Graphics。它只是在drawString等画字符串方法的地方将这些字符串记录下来,并包括其边界,以供处理器根据鼠标位置确定鼠标所在位置下的文字。

    这个ProxyGraphics是整个屏幕取词核心思想,相当于金山词霸中做的钩子。当鼠标移动到某个组件上时,这个鼠标处理事件通过下面的代码来获取该组件上渲染的文字:

Image offImage = component.createVolatileImage(component.getWidth(), component.getHeight());
 if (offImage != null) {
      ProxyGraphics pg = new ProxyGraphics((Graphics2D) offImage.getGraphics());
      ArrayList dbcomponents = new ArrayList();
      clearDoubleBuffer(component, dbcomponents);
      paintComponent(pg);
      resetDbl(dbcomponents);
      texts = pg.getTexts();
}

    首先它创建虚屏offImage,之后通过new ProxyGraphics((Graphics2D) offImage.getGraphics())封装该虚屏的图形对象,然后将之传给这个component的paintComponent方法渲染。由于是渲染到虚屏上的,所以不会画到实际屏幕上,弄糟这个屏幕。这个过程中ProxyGraphics截获了所有对于drawString的调用,并把被渲染文本和位置信息记录下来,最后返回给鼠标移动处理器。以后鼠标移动事件处理就可以根据这些信息决定鼠标所在位置的文本。

    上面是大致思想。当然实际过程要复杂的多,比如禁止双缓冲,create()方法的继续被封装。如果考虑更复杂的情况,比如考虑界面上如果有动态文字的情况等等,程序将变得非常复杂。这儿由于只是做个演示,表达一种思想,因此没有写的太复杂。主要接口类是MouseListenerTrigger,启动屏幕取词的方法是在应用程序启动前加入这样一句:

Toolkit.getDefaultToolkit().addAWTEventListener(
                new MouseListenerTrigger(), AWTEvent.MOUSE_MOTION_EVENT_MASK);

    现实应用场景中,应该有一个安装程序,将这个类作为JRE扩展安装上去。这样凡是使用这个经过扩展JRE的Swing应用就有了屏幕取词的功能。

    这个例子中的辞典是dyno.swing.beans包下的一个dictionary.xml配置文件。作为实验,可以在里面加入其他的单词,并把这个上面的语句加入现有的Swing程序之前观看结果。

    下面是我做的这个演示的一个截图:

源代码下载