一、摘要
这篇文章将介绍可升级2D矢量图形API (Scalable 2D Vector Graphics API),它是J2ME的一个可选包。这些API非常易于使用,它们可以用来渲染和转换体积小和伸缩性好的二维(2D)图形,这个可选包在java社区进程(JCP)中以JSR-226开发。我将会给出API的总括,并且突出典型的用户案例,使用代码演示这些案例。
二、慨述
JSR-226将渲染和重放矢量2D图形引入到Java语言的移动应用中。由Nokia领导的专家组,是由移动行业的主要厂商组成,他们都很有爱好将矢量多媒体内容引入到他们的设备中。在写这篇文章时,这个规范刚刚被核准,大家正在热切期待着一个参考实现。
矢量图对移动开发者来说,有两大好处:占用空间小和伸缩性好。考虑下面这个简单的图像:
一个简单的示例图像
图1 一个简单的示例图像
使用GIF格式,这个图片的大小会达到7386字节。使用向量格式,大小仅仅为693字节,不到十分之一。这种减少是如何发生的呢?
以光栅为基础的图像格式,像GIF,对组成图像的矩形区域中的每一个像素的色彩内容进行编码。而矢量图,仅仅包含决定像素应该如何被着色的绘画指令。一个图像的向量表示法可以更大程度上进行压缩,这对资源受限的移动设备是一个很大的有利因素。
伸缩性好是另外一个重要的优点。矢量图可以很清楚的进行变换,因为它们的绘画指令是和分辨率无关的。图2显示前一张图片进行等比例缩小、翻转和旋转后的图像。
示例图片的变换
图2 示例图片的变换
矢量图的度量单位是任意的和相对的,它们可以被一些常量因子乘或除,以变换图像从而适应特定设备的屏幕。通过对这个坐标系统使用简单的数学操作,你可以创造出无限的效果,包括翻转、旋转、拉伸和扭曲。使用矢量图像,应用程序开发者不再需要为图像和图标创建不同的集合,来支持不同的屏幕分辨率。
矢量图的简洁和伸缩性在动画领域大放异彩。以光栅为基础的动画,像网页中的GIF和MIDP游戏中的精灵,在动画的每一个帧中包含整个图片都很费力。与之相比,以矢量为基础的动画仅仅包含一些指令,而这些指令规定了当需要改变动画时的图像哪些元素应该如何改变。假如矢量图的大小比它们相对应的光栅图小一个数量级,那么矢量动画的大小就比他们相对应的光栅动画小两个数量级,这使得它们成为在移动设备上变换和展示多媒体内容的理想选择。
当以私有的Macromedia Flash播放器为基础的矢量图和矢量动画变得流行时,可升级矢量图形(Scalable Vector Graphics,SVG)文件格式以开放的标准和免费的专利权,成为业界的另外一个选择。它由W3C标准化。SVG-Tiny提供了SVG全部功能的一个子集,适合在移动设备上使用。JSR-226采用SVG-Tiny的1.1版本作为J2ME矢量图形的官方文件格式。W3C称SVG-Tiny为一个“PRofile”,但是为了避免和J2ME的Profile混淆,我称之为“格式”。
SVG-Tiny标准也可以支持动画。图像文件内部的指令可以自己修改图像元素的位置和属性,往返应时钟事件和用户输入。
为了更加复杂的交互,JSR-226引入了兼容microDOM的API,microDOM是SVG1.2完整的文档对象模型(DOM)的一个子集。
SVG是一种可扩展标记语言(xml)格式,它使用一个公共的Schema充分的注释和系统化。SVG-Tiny可能很小,但是它们仍然是SVG文件,仍然包含XML。下面是代表一个“brave world”图像的完整的SVG-Tiny文件:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg preserveaspectRatio="xMidYMid meet"
viewbox="10 10 130 55" width="150.0" height="75.0">
<rect x="25" y="25" transform="translate(75 36.375)
rotate(15) translate(-70 -35.375)" fill="#00007E"
width="100" height="22.75" stroke="#000000" stroke-width=".5"/>
<rect x="25" y="25" fill="#FF9800" width="108.5"
height="22.75" stroke="#000000" stroke-width=".5"/>
<text x="30" y="40" fill="#00007E" stroke="#FFFFFF"
stroke-width=".33" xml:space="preserve">Hello brave world.</text>
</svg>
这个文档声明了两个矩形和一些文字。第一个矩形绕它的中心旋转15度,这一系列的转换都包含在rect标记的转换属性中。第二个矩形在第一个之上绘制,然后文本在第二个矩形之上绘制。
即时未经修饰的文本不是一个非凡的简洁格式,这个文档仍然比一个压缩的光栅图像小的多,并且易于人工导入。因为XML文本可以充分地压缩,所以SVG在应用程序的JAR文件中就占用相对较少的空间。JSR-226要求支持SVG文档的实现使用gzip格式压缩。后缀名.svgz表示gzip压缩的文档。
三、Mobile 2D API
JSR-226的一个主要的目的就是定义移动2D图形(Mobile 2D Graphics,M2G)API,这个API是一小组和SVG-Tiny格式功能结合很紧密的类。它没有被规定为普通的2D图形绘制工具箱,就像 J2SE的Graphics2D类提供的那样。M2G API很清楚地定位于播放和在运行期操纵SVG内容。
M2G由高级类和低级类组成,高级类用来创建和渲染矢量图形,而低级类负责以DOM树的部分来操纵一个矢量图形的XML成分。矢量图形是ScalableImage的实例,你可以通过这个的静态createImage()方法获得它们。你使用ScalableGraphics的一个实例将ScalableImages绘制到一个 MIDP图形上下文中。SVGImage是ScalableImage的一个子类,它提供事件处理和底层DOM文档访问之间的联系。
为了使渲染动画SVG内容的普通情况更加方便,这个API提供了SVGAnimator类。对MIDP应用程序,SVGAnimator创建和控制一个 Canvas对象,这个对象自动处理屏幕更新,以回应动画事件和对这个图像的计划性的修改。SVGAnimator提供一个类似播放器接口来控制动画的播放。
这些类都定义在javax.miroedition.m2g和org.w3c.dom.svg包中,在表1中进行了总结,并且在 JSR-226规范中也作了充分的注释。SVG DOM是在标准的org.w3c.domorg.w3c.dom.events包中定义DOM类和接口的扩展。
Class或Interface 描述
javax.microediton.m2g
ScalableGraphics 2D渲染的基础类
SVGAnimator 这个类对一个目标用户接口组件,处理SVGImage的更新和动画的自动渲染
SVGEventListener 这个接口用来处理一个应用程序的平台相关事件
SVGImage 这个类代表符合W3C SVG Tiny1.1 Profile的SVG图像
ExternalResourceHandler 这个接口用来同步加载装入SVG内容所需要的外部资源
org.w3c.dom.svg
SVGAnimatorElement 这个接口代表一个动画元素,包含控制动画时间的方法
SVGElement 这个接口代表文档树中一个SVG元素
SVGLocatableElement 这个接口代表一个可绘制的SVG元素,典型的有外形、图像或者文本
SVGMatrix 这个接口代表一个SVG matrix数据类型,由一个仿射矩阵定义,等价于一个平移的线性变换
SVGPath 这个接口代表一个SVG path数据类型,用来定义几何路径
SVGPoint 这个接口代表一个SVG point数据类型,由它的x和y标示
SVGRect 这个接口代表SVG rectangle数据类型,由最小X、最小Y、宽度和高度组成
SVGRGBColor 这个接口代表SVG RGB color数据类型,由红、绿、蓝组成
SVGSVGElement 这个接口代表在SVG文档树中的一个元素
四、创建M2G应用程序
为了创建一个M2G应用程序,你需要可以工作的JSR-226实现。在写这篇文章时,这个规范才刚刚被定案;没有设备实现它,并且没有官方的参考实现可用。你可以下载一个有限的、实现的实现,作为mpowerplayer开发者工具箱的附加包。
你还需要一些SVG-Tiny内容。这篇文章是用上面的“brave world”图像,但是更多的SVG-Tiny示例可以从移动开发工具制造者TinyLine上获得。你也可以使用一个商用Java应用程序创建自己的内容,这个应用程序被称为Sketsa,它可以像一个插图工具一样工作,并且使用SVG作为它的文件格式。
只有很少的方法用来将你的 SVG内容绘制到屏幕上。最简单的是为你的图像创建一个SVGAnimator,然后把它Canvas放到屏幕上。另外一个方法是创建一个 ScalableImage的实例,然后使用ScalableGraphics的实例将它绘制到你自己的Canvas或者CustomItem的图形上下文中。你将会获得除了展现外的更多控制,但是你必须负责处理输入事件,并且为动画效果或者用户交互进行重绘。
五、使用一个SVGAnimator显示2D图像
你可以通过SVGAnimator的静态方法createAnimator()获得一个它的实例,需要提供你自己的图像。使用SVGAnimator是一个不错的选择,因为它处理所有的用户交互,和动态内容的动画。play()、pause()、stop()和setTimeIncrement()方法会给你除了重放之外的对动画的可编程控制。实现可能提供一个本地用户接口,可以同样的处理动态行为。
一旦你获得SVGAnimator,就可以很轻易的将它嵌入到你的应用程序中。调用getTargetComponent()会一个Canvas实例,它可以被放置在屏幕上。下面是一个例子:
public void animationTest()
{
// create an animator to load the content
SVGAnimator animator =
SVGAnimator.createAnimator( image );
// add our custom event listener
animator.setEventListener(
new CustomEventListener( animator ) );
// get the Canvas for this player; requires a cast
Canvas canvas = (Canvas) animator.getTargetComponent();
// add a "back" command
canvas.addCommand( backCommand );
canvas.setCommandListener( this );
// show it
Display.getDisplay(this).setCurrent( canvas );
// start it
animator.play();
}
在MIDP环境中,从getTargetComponent()返回的值是一个MIDP Canvas。在非MIDP环境中,返回值将适合本地窗口系统,例如在AWT环境中,返回值是Component。假如当前环境拥有一个以上的可用的选择,那么就提供需要的类型的名字作为createAnimator()的第二个参数。
图3显示了运行时结果。根据不同的实现,和笔触设备的可用性,动画绘制器可能答应用户和动画内容进行交互。在“brave world”例子中没有动态的内容,但是文本是可选的。
由SVGAnimator控制的Canvas
图3 由SVGAnimator控制的Canvas
六、和动画绘制器交互
尽管“brave world”图像不是一个动画,一些程序设计还是可以增加交互的。
JSR-226的DOM支持一个程序改变一个图像的结构。和任何其他的XML文档一样,一个SVGImage可以用一个DOM对象树代表。M2G API答应你使用类似熟悉的XML处理技术来修改图像。树中的每一个节点是一个SVGElement,提供操纵临近的父节点和子节点的方法。你可以通过添加和删除子节点修改节点,或者转换成一个合适的元素类,然后调用那个类的方法进行改变。
SVGEventListener结构使你的应用程序可以从SVGAnimator的Canvas接收用户的输入事件。这个例子是用DOM API来等比例变化和旋转这个图像,用户可以通过箭头键输入:
{
int gameAction = canvas.getGameAction( keyCode );
switch ( gameAction )
{
case Canvas.UP:
scale( 0.10f );
break;
case Canvas.LEFT:
rotate( -10.0f );
break;
case Canvas.DOWN:
scale( -0.10f );
break;
case Canvas.RIGHT:
rotate( 10 );
break;
default:
// leave unchanged
}
}
private void rotate( final float delta )
{
// put ourselves on the animator's thread
animator.invokeLater( new Runnable()
{
public void run()
{
// execute the transformation
Document document = svgImage.getDocument();
SVGSVGElement root = (SVGSVGElement)
document.getDocumentElement();
root.setCurrentRotate( root.getCurrentRotate() + delta );
}
} );
}
private void scale( final float delta )
{
// put ourselves on the animator's thread
animator.invokeLater( new Runnable()
{
public void run()
{
// execute the transformation
Document document = svgImage.getDocument();
SVGSVGElement root = (SVGSVGElement)
document.getDocumentElement();
root.setCurrentScale( root.getCurrentScale() + delta );
}
} );
}
SVGImage.getDocument()提供了动画绘制器渲染的文档的引用。因为这个播放器可能正在运行一个或者更新一个动态的内容,所以你必须小心地将你的变化和动画线程同步,否则将会发生不可确定的错误。SVGAnimator's invokeAndWait() 和invokeLater()方法就像它们的AWT副本一样,可以保证你的代码在动画线程上运行。
七、直接渲染2D内容
假如你想除了你的SVG图像在哪里和怎样被渲染外获得完全的控制,你可以使用ScalableGraphics类直接绘制一个ScalableImage到Graphics对象之上,这个Graphics对象被传递到 Canvas、Layer或者CustomItem实例的paint()方法中。
你通过调用ScalableGraphics的静态 createInstance()方法创建一个它的实例。在绘制之前,你必须首先使用bindTarget()方法将ScalableGraphics实例和传递到绘制方法中的Graphics对象绑定。在完成以后,请调用releaseTarget()。一旦建立绑定,你就可以使用render()方法绘制你的每一个图像。
这个例子以不同的大小重复绘制“brave world”图像,例子通过在每一次调用render()方法前调用setViewportHeight()和setViewportWidth()方法改变图像大小:
private static class M2GCanvas extends Canvas
{
// retain a reference the specified image
ScalableImage scalableImage;
// retain an instance of a scalable graphics
ScalableGraphics scalableGraphics;
public M2GCanvas( ScalableImage inImage )
{
scalableImage = inImage;
// create the scalable graphics instance
scalableGraphics = ScalableGraphics.createInstance();
}
public void paint( Graphics g )
{
// clear the display
g.setColor( 255, 255, 255 );
g.fillRect( 0, 0, getWidth(), getHeight() );
// bind our scalable graphics to the given graphics
scalableGraphics.bindTarget( g );
// render at fixed position and size
scalableImage.setViewportWidth( 50 );
scalableImage.setViewportHeight( 75 );
scalableGraphics.render( 5, 50, scalableImage );
// again at different position and size
scalableImage.setViewportWidth( 100 );
scalableImage.setViewportHeight( 150 );
scalableGraphics.render( 80, 5, scalableImage );
// again at size that varies with the canvas size
scalableImage.setViewportWidth( getWidth()-20 );
scalableImage.setViewportHeight( getHeight()-20 );
scalableGraphics.render( 0, 0, scalableImage );
// release the graphics context
scalableGraphics.releaseTarget();
}
}
Canvas的子类在不同的位置以不同的大小显示这个图像三次。在第三次,图像填满了这个画布。在支持可重设屏幕大小的设备上,图像可以适当的等比例变化。图4显示了画布的效果。在这个实现中,较大的图像在窗口大小被重置时等比例得到。
在一个Canvas上等比例变换ScalableImages
图4 在一个Canvas上等比例变换ScalableImages
假如你正在渲染的图像是动态的,你应该答应用户和他们交互。当你不用SVGAnimator渲染时,你需要检测用户指针事件,得到图像是否和在何处被点击,然后在SVGImage上调用dispatchMouseEvent()方法,触发脚本定义好的动作。对一个CustomItem,表单移动应该答应激活和焦点导航,通过图像中的元素,分别使用SVGImage的activate() 和focusOn()方法。
你的程序不仅仅可以操纵一个已经存在的SVGImage的DOM,也可以从头建立一个图像。SVGImage的静态方法createEmptyImage()返回一个具有空文档的图像,这个文档的架构可以有程序创建的代表外形和变换的SVGElement填充。这种可以和ScalableGraphics联合使用,实现一个普通的2D绘制工具箱,但是正像我前面提到的那样,这样用法不是最初的意向,不推荐使用。
八、总结
J2ME的可升级2D矢量图形API提供了渲染和操作以矢量为基础的图像和动画的能力。矢量图像的占用空间小和伸缩性好使得它天生的适合移动应用程序。JSR-226的移动2D图形API使应用程序能够使用MIDP和AWT工具箱创建、显示和修改SVG内容。