文/朱先忠编译
把纹理链接到四边形上
把纹理图像平铺到一个四边形上的常用方法是,把纹理的左下角链接到四边形的左下角,并以反时针方向指定其它链接。这种方法显示在图4中。
图 4.纹理与四边形间的标准链接
纹理坐标的范围是0-1,沿着x轴和y轴,且y轴方向向上。例如,纹理左下角使用坐标(0,0),则右上角为(1,1)。
当使用"Y-up"方式时,y轴的纹理坐标是颠倒的,即指向下方。这就是说,坐标(0,0)对应纹理的左上角,而(1,1)对应纹理的右下角。
在使用"Y-up"方式情况下,纹理坐标一定要赋给四边形的不同点以取得图像的相同的方向。这种新的配置显示在图5中。
图 5. 当使用"Y-up"方式时的纹理与四边形的链接
在JMFMovieScreen中,实现把四边形顶点与纹理坐标相链接的代码如下:
TexCoord2f q = new TexCoord2f();
q.set(0.0f, 0.0f); plane.setTextureCoordinate(0, 3, q); //纹理坐标(0,0)-->四边形左上点(p3) q.set(1.0f, 0.0f); plane.setTextureCoordinate(0, 2, q); // (1,0) -->右上(p2)
q.set(1.0f, 1.0f); plane.setTextureCoordinate(0, 1, q); // (1,1) -->右下(p1)
q.set(0.0f, 1.0f); plane.setTextureCoordinate(0, 0, q); // (0,1) -->左下(p0) |
这里的平面对象代表了四边形。
更新图像
前面已经提到,一个TimeBehavior对象被建立以每隔40毫秒调用一次JMFMovieScreen的nextFrame()方法。而nextFrame()又调用JMFSnapper对象的getFrame()方法来以一个BufferedImage对象方式检索当前动画帧。该BufferedImage对象被指派给ImageComponent2D对象,然后用于四边形的材质。nextFrame()的代码如下所示:
//全局变量 private Texture2D texture; //由四边形使用 private ImageComponent2D ic;
private JMFSnapper snapper; //快照该动画 private boolean isStopped = false; //动画停止了吗?
public void nextFrame() { if (isStopped) //动画已经停止 return;
BufferedImage im = snapper.getFrame(); //获取当前帧 if (im != null) { ic.set(im); //把该帧指派给ImageComponent2D texture.setImage(0,ic); //使成为该形状的材质 } else System.out.println("Null BufferedImage"); } |
JMFSnapper对象snapper是在JMFMovieScreen的构造器中创建的:
//装载并播放动画 snapper = new JMFSnapper(movieFnm); |
JMFSnapper中的简单接口隐藏了用于播放动画和从动画中提取帧的JMF代码的复杂性。在本文的第二部分里,JMFSnapper类为一个使用QuickTime for Java的版本所取代,且JMFMovieScreen类也作了最少的修改。
5.管理动画
JMF为存取特定的动画帧提供了一种高级存取方式。下面的代码片断显示了该高级方式的主要组成,我略去了其中有关错误检测及异常处理的部分。
//在realized状态下,创建一个动画播放器 URL url = new URL("file:" + movieFnm); Player p = Manager.createRealizedPlayer(url);
//生成一个帧放置器 FramePositioningControl fpc = (FramePositioningControl) p.getControl("javax.media.control. FramePositioningControl");
//创建一个帧抓取器 FrameGrabbingControl fg = (FrameGrabbingControl) p.getControl("javax.media.control.FrameGrabbingControl");
//要求改变到一个prefetched 态 p.prefetch();
//一直等待,直到播放器处于那种状态...
//移动到具体的某帧,例如第100帧 fpc.seek(100);
//取得当前帧的一个快照 Buffer buf = fg.grabFrame();
//取得它的视频格式细节 VideoFormat vf = (VideoFormat) buf.getFormat();
//用视频格式初始化BufferToImage BufferToImage bufferToImage =new BufferToImage(vf);
//把缓冲区数据转化成一幅图像 Image im = bufferToImage.createImage(buf);
//指定想得到的BufferedImage的格式 BufferedImage formatImg = new BufferedImage( FORMAT_SIZE, FORMAT_SIZE, BufferedImage.TYPE_3BYTE_BGR);
//把该图像转化成一个BufferedImage Graphics g = formatImg.getGraphics(); g.drawImage(im, 0, 0, FORMAT_SIZE, FORMAT_SIZE, null); g.dispose(); |
一个媒体播放器从创建到开始播放共经历6种状态。处于realized态的播放器知道如何对其数据进行着色,所以在要求时可以提供可视化组件和控件。我用了两个控件:FramePositioningControl 和FrameGrabbingControl
FramePositioningControl提供seek()和skip()等方法,用于在一个动画中移动以查找一个特别的帧。FrameGrabbingControl提供了方法grabFrame(),它可以从动画的视频轨道中抓取当前帧。
为使这些控件工作,播放器必须实现从realized 态转入prefetched 态。这可以使播放器为进行媒体播放作好准备,并使媒体数据装入。
对于prefetch()的调用是异步的,这意味着我的代码必须包含一个等待周期,直到完成一个变换状态为止。标准的JMF编码方案是实现一个waitForState()方法,它可以停止代码的执行,直到一个状态改变事件唤醒它。
要抓取的帧可以用seek()方法在轨道中定位,然后调用grabFrame()方法实现帧的抓取。编码中必须经历多个转换步骤来把抓取的缓冲对象转换成JMFMovieScreen要求的BufferedImage对象。注意,BufferedImage对象使用了TYPE_3BYTE_BGR格式,这种格式对于该程序中的Java 3D部分通过引用方式使用纹理是必需的。
Sun的JMF站点 包括了一些有用的小例子,其中Seek.java一例说明了如何使用FramePositioningControl来遍历一个动画。