· 動畫屏幕是視圖元素,由JMFMovieScreen類描述。
· 動畫是模型部分,由JMFSnapper類所管理。
· 一個Java 3D Behavior類TimeBehavior是控制器,由它完成從動畫中的周期性幀檢索,然后在屏幕上畫出。
在這篇文章中,我將revisit該動畫組件,用 QuickTime for Java (QTJ)來重新實(shí)現(xiàn)之。QTJ在QuickTime API之上提供了一個面向?qū)ο蟮腏ava 層,使之有可能實(shí)現(xiàn)播放,編輯和創(chuàng)建QuickTime 動畫;捕獲音頻與視頻;執(zhí)行2D和3D動畫。QuickTime在Mac 和Windows上平臺都可以使用。關(guān)于QTJ的安裝,文檔和舉例的細(xì)節(jié)信息請參見developer.apple.com/quicktime/qtjava。
由QTJ 來取代JMF 設(shè)計(jì)模式的結(jié)果對應(yīng)用程序影響很小-只有動畫類JMFSnapper分離出來,由QuickTime for Java版本的QTSnapper所代替。
圖 1展示了QTJ 版本的Movie3D應(yīng)用程序中的兩幅屏幕快照,其中右邊的那幅是屏幕從后面看上去的視圖。
![]() 圖 1.QTJ Movie3D應(yīng)用程序中的兩幅視圖 |
如果快點(diǎn)回顧一下第一部分中的圖1,你會發(fā)現(xiàn)基于QTJ的應(yīng)用程序和JMF 版本的實(shí)現(xiàn)沒有太明顯的區(qū)別。
但是,如果細(xì)致比較一下這兩處執(zhí)行程序會發(fā)現(xiàn)有兩個變化:QTJ版本的動畫像素化pixelation 更明顯,且播放速度更慢些。像素化的出現(xiàn)是由于原始的動畫被從MPEG 格式翻譯成QuickTime的MOV格式所致,可以借助于一個更好的轉(zhuǎn)換工具來實(shí)現(xiàn)修補(bǔ)。速度問題更為根本性:它與QTSnapper的基本實(shí)現(xiàn)有關(guān)。
該文中主要涉及到:
·討論QTSnapper實(shí)現(xiàn)中的兩種主要方法。一種方法是把動畫的每一幀都著色到屏幕上去,另一種方法是基于當(dāng)前時(shí)間選擇一幀。 后一種方法意味著,可以跳過一些幀,使得動畫顫動一點(diǎn),但是卻使動畫播放速度加快。
·介紹幾種簡單的幀/秒(FPS)計(jì)算方法,我將用之來判斷不同實(shí)現(xiàn)方式的相對速度的不同,并用來檢測跳過的幀。
我不會再細(xì)致地介紹動畫屏幕和動畫更新行為,因?yàn)檫@些與第一部分中是一致的。
下面我將詳細(xì)介紹的是我應(yīng)用在QTSnapper中的用于從動畫中提取幀的QTJ技術(shù)。
1. 程序?qū)崿F(xiàn)中的兩幅輪廓圖
下面的圖2描述了該應(yīng)用程序中的場景圖。
![]() 圖 2. Movie3D場景圖 |
該圖幾乎和第一部分中的一模一樣。
QuickTime動畫由QTSnapper類負(fù)責(zé)裝載。動畫屏幕由QTMovieScreen創(chuàng)建,它管理一個放置在跳棋盤地板上的Java 3D四邊形。每隔40毫秒,TimeBehavior 對象調(diào)用QTMovieScreen中的nextFrame()方法一次,該方法調(diào)用中QTSnapper 的getFrame()方法來取得動畫中的一幀,該幀最后被放置到由QTMovieScreen管理的四邊形上。
JMFSnapper和QTSnapper之間有一個重要的不同。JMFSnapper返回當(dāng)前正播放動畫的當(dāng)前幀,而QTSnapper依賴于一個遞增的索引值返回動畫中的當(dāng)前幀。
例如,當(dāng)getFrame()在JMFSnapper中被反復(fù)調(diào)用時(shí),它可以檢索幀1,3,6,9,11,等等,具體依賴于調(diào)用的方法和動畫的播放速度。當(dāng)在QTSnapper中調(diào)用getFrame()時(shí),它將返回幀1,2,3,4,等等。
下面的圖3描述了該應(yīng)用程序的UML類圖,其中僅顯示了類的公共方法。
![]() 圖 3. Movie3D類圖 |
除了動畫屏幕QTMovieScreen類和動畫QTSnapper類名字的區(qū)別外,這里的應(yīng)用程序類繼承圖與第一部分中的沒有區(qū)別。事實(shí)上,只有Snapper類的內(nèi)部實(shí)現(xiàn)變更了一些。
把應(yīng)用程序從JMF Movie3D版本遷移到QTJ版本要求代替Snapper類。另外,還需要把動畫屏幕類中的兩行代碼改變一下,其中聲明了Snapper 類并被實(shí)例化:
//全局變量定義 private QTSnapper snapper; //以前是JMFSnapper //在構(gòu)造器中以fnm方式裝入動畫 snapper = new QTSnapper(fnm); |
這兩處變化是把JMFMovieScreen 改名為QTMovieScreen的唯一原因。
所有該示例的代碼以及本文的一個早期版本,都能在KGPJ website處找到。
2. 一部幀到幀F(xiàn)rame-By-Frame 的動畫
理解了QTSnapper的內(nèi)部工作機(jī)理,有助于對QuickTime動畫的結(jié)構(gòu)有一個基本了解。每個動畫可能由多個音頻和視頻軌道合成,而在時(shí)間上相重疊。下圖4展示了這種基本思想。
![]() 圖 4.一個QuickTime動畫的內(nèi)部結(jié)構(gòu) |
每個軌道管理自己的數(shù)據(jù),如它包含的媒體的類型和媒體本身。該媒體容器具有它自己的數(shù)據(jù)結(jié)構(gòu),包括持續(xù)時(shí)間和播放速率(也就是,每秒要顯示多少個樣本)。這里的媒體實(shí)際上是一系列的樣本(或者幀),第一個樣本從時(shí)間0開始(with respect to media time)。樣本被索引化了,如第一個樣本在位置1處(而不是0)。
簡化后的QuickTime軌道和媒體結(jié)構(gòu)如下圖5所示。
![]() 圖 5. 一個QuickTime軌道和媒體的內(nèi)部結(jié)構(gòu) |
要想更全面的了解,請參考QuickTime教程的動畫部分。
存取動畫的視頻媒體
在QTSnapper類構(gòu)造器中打開動畫:
// 全局變量 private boolean isSessionOpen = false; private OpenMovieFile movieFile; private Movie movie; //在構(gòu)造器中啟動一個QuickTime會話 QTSession.open(); isSessionOpen = true; //打開動畫 movieFile =OpenMovieFile.asRead( new QTFile(fnm) ); movie = Movie.fromFile(movieFile); |
QTSession.open()的執(zhí)行來實(shí)現(xiàn)使用QuickTime 之前對其初始化。在程序最后的終止時(shí)刻應(yīng)該有一個相應(yīng)的QTSession.close()調(diào)用。
視頻軌道的定位(如果存在的話)及其媒體的存取如下:
//更多的全局變量 private Track videoTrack; private Media vidMedia; //在構(gòu)造器中,從動畫中提取視頻軌道 videoTrack = movie.getIndTrackType(1, StdQTConstants.videoMediaType, StdQTConstants.movieTrackMediaType); if (videoTrack == null) { System.out.println("Sorry, not a video"); System.exit(0); } //取得由視頻軌道使用的媒體 vidMedia = videoTrack.getMedia(); |
一旦媒體被暴露出來exposed,就可以從中提取各種信息:
//更多的全局變量 private MediaSample mediaSample; private int numSamples; //樣本數(shù) private int sampIdx; //當(dāng)前樣本索引 private int width; // 幀寬度 private int height; //幀高度 //在構(gòu)造器中 numSamples = vidMedia.getSampleCount(); sampIdx = 1; //取得軌道中的第一個樣本 mediaSample = vidMedia.getSample(0, vidMedia.sampleNumToMediaTime(sampIdx).time,1); //把圖像的寬度和高度值存儲在該樣本中 ImageDescription imgDesc = ImageDescription) mediaSample.description; width = imgDesc.getWidth(); height = imgDesc.getHeight(); |
sampIdx用作計(jì)數(shù)器,實(shí)現(xiàn)樣本(第一個樣本從位置1開始)的反復(fù)提取。
動畫圖像的寬度和高度信息通過分析第一個樣本得到,不過假設(shè)前提是所有的樣本使用相同的尺寸。
計(jì)算FPS
由QTSnapper返回的每秒幀數(shù)將用于后面的對該類不同實(shí)現(xiàn)策略的比較。在構(gòu)造器中實(shí)現(xiàn)必需元素的初始化的代碼如下:
//幀速率全局變量 private long startTime; private long numFramesMade; //在構(gòu)造器中初始化它們 startTime = System.currentTimeMillis(); numFramesMade = 0; |
收尾處理
當(dāng)應(yīng)用程序即將終止時(shí),QTSnapper中的stopMovie()方法被調(diào)用,它給出FPS值并關(guān)閉QuickTime。
// 全局變量 private DecimalFormat frameDf=new DecimalFormat("0.#");//1dp synchronized public void stopMovie() { if (isSessionOpen) { //報(bào)告幀速率 long duration =System.currentTimeMillis() - startTime; double frameRate = ((double) numFramesMade*1000.0)/duration; System.out.println("FPS: " +frameDf.format(frameRate)); QTSession.close(); //關(guān)閉QuickTime isSessionOpen = false; } } |
stopMovie()和getFrame()均被同步化處理,以確保正在從動畫中進(jìn)行幀復(fù)制時(shí)不會出現(xiàn)QuickTime會話終止的情形。
抓幀
getFrame()方法從動畫中以BufferedImage 對象的形式返回一個樣本(一幀)。該幀的選取是通過存儲在變量sampIdx(其取值范圍從1到numSamples,然后重復(fù))中的索引號實(shí)現(xiàn)的。
// 全局變量 private BufferedImage img, formatImg; synchronized public BufferedImage getFrame() { if (!isSessionOpen) return null; if (sampIdx > numSamples) //往回從第一個樣本重新開始 sampIdx = 1; try { /*取得從指定的索引時(shí)間開始的樣本*/ TimeInfo ti = vidMedia.sampleNumToMediaTime(sampIdx); mediaSample=vidMedia.getSample(0,ti.time,1); sampIdx++; writeToBufferedImage(mediaSample, img); //調(diào)整img的大小,把它寫入formatImg Graphics g = formatImg.getGraphics(); g.drawImage(img,0,0,FORMAT_SIZE,FORMAT_SIZE,null); //覆蓋掉圖像上的當(dāng)前時(shí)間 g.setColor(Color.RED); g.setFont(new Font("Helvetica", Font.BOLD, 12)); g.drawString(timeNow(), 5, 14); g.dispose(); numFramesMade++; //幀計(jì)數(shù) } catch (Exception e) { System.out.println(e); formatImg = null; } return formatImg; } //getFrame()方法結(jié)束BufferedImage |
通過調(diào)用QTJ媒體類中的getSample()方法可以很容易地得到樣本。不幸的是,把樣本轉(zhuǎn)化成BufferedImage仍然存在一定的困難--我把這些都藏在了我的writeToBufferedImage()方法的實(shí)現(xiàn)之中了。
這個方法的實(shí)現(xiàn)使用一些奇特的辦法,這是從Chris W.Johnson的MovieFrameExtractor.java例子(你可以在quicktime-java 郵件列表中找到)中遷移過來的。
比較奇特的實(shí)現(xiàn)思路和細(xì)節(jié)!但都加上了良好的注釋,你可以詳細(xì)地研究這些代碼。一個"原始的"圖像從樣本中提取出來,然后當(dāng)被寫入一個QuickTime 版本的Graphics 對象時(shí)解壓。Graphics對象中未壓縮的數(shù)據(jù)被復(fù)制到另一個"原始"圖像中去,然后又被復(fù)制到一個像素?cái)?shù)組(一個PixMap)中去。最后,該數(shù)組被寫進(jìn)一個空的BufferedImage的DataBuffer部分。
應(yīng)用程序能運(yùn)行嗎?是否運(yùn)行良好?
的確,Movie3D可以播放動畫,但是大型的動畫播放起來速度很慢。這是由于getFrame()方法提供幀的速度太慢了,此可以通過觀察FPS來定量地計(jì)算出。
對于圖1中的動畫,程序在一臺較慢的Windows 98機(jī)器上報(bào)出的FPS范圍在每秒15到17幀。然而,TimeBehavior對象要求每40毫秒作一次更新,這轉(zhuǎn)換成幀速的話,看上去大約接近25 FPS。
由于耗時(shí)的樣本到BufferedImage的轉(zhuǎn)換,getFrame()方法運(yùn)行緩慢。由于當(dāng)前對getFrame()的調(diào)用在作幀轉(zhuǎn)化時(shí)陷于困境,所以下一步的請求因等待當(dāng)前請求的完成而被耽誤。
下面,我將分析克服這一問題的兩種方法:其一是,當(dāng)最終開始處理某一請求時(shí),允許getFrame()進(jìn)行幀跳躍處理;其二是,在getFrame()中嘗試用一種不同的轉(zhuǎn)換策略。下面我將輪流分析這兩種方法,先看幀跳躍處理。
3. 幀跳躍處理的動畫
新的Snapper類,QTSnapper1,在調(diào)用方法getFrame()時(shí)仍然返回一幀。該類與QTSnapper的不同在于,它相應(yīng)于動畫的當(dāng)前運(yùn)行時(shí)間而提供一幀。
例如,getFrame()方法可能檢索第1,2,5,8,12幀,等等,具體依賴于什么時(shí)候調(diào)用該方法。這樣以來,動畫以良好的速度播放,但是漏掉的幀可能使得動畫出現(xiàn)顫動現(xiàn)象。
相比較之下,QTSnapper將返回所有的幀(1,2,3,4,等等),但是在getFrame()方法調(diào)用之間的延誤將使得動畫運(yùn)行緩慢。只是由于沒有砍掉幀,所以不會出現(xiàn)一些顫動現(xiàn)象。
QTSnapper1中的一個關(guān)鍵元素是動畫的"當(dāng)前運(yùn)行時(shí)間"的概念。我采用的解決途徑是,當(dāng)調(diào)用getFrame()方法時(shí)為QTSnapper對象計(jì)算當(dāng)前運(yùn)行時(shí)間,并把它轉(zhuǎn)換成一個動畫時(shí)間,最后轉(zhuǎn)換成一個樣本索引值。
QTSnapper1與QTSnapper一樣有相同的公共方法,所以只要最少的修改即可應(yīng)用在QTMovieScreen中。只有在動畫播放時(shí)它們的區(qū)別才變得明顯起來。在后面將詳述的定量計(jì)算表明,現(xiàn)在的QTSnapper1方法顯示圖1中"表面上"的幀速率達(dá)21 FPS,而QTSnapper方法只能達(dá)到幀速率16 FPS。
存取動畫的視頻媒體
QTSnapper1遵循了與QTSnapper相同的步驟來實(shí)現(xiàn)動畫視頻的存取。一旦視頻可用,好幾個媒體值作為全局變量存儲,以備后面getFrame()方法所用:
// 全局變量 private Media vidMedia; private int numSamples; private int timeScale; //媒體的時(shí)間刻度 private int duration; //媒體的持續(xù)時(shí)間 //在構(gòu)造器中,取得由視頻軌道使用的媒體 vidMedia = videoTrack.getMedia(); //存儲視頻細(xì)節(jié)以備后用 numSamples = vidMedia.getSampleCount(); timeScale = vidMedia.getTimeScale(); duration = vidMedia.getDuration(); |
獲取幀
getFrame()方法中的新的編碼部分在于如何計(jì)算用來存取一個特別樣本的索引值;而該方法中的其余部分,對于writeToBufferedImage()的調(diào)用,以及把當(dāng)前時(shí)間寫到圖像上等等,都和QTSnapper中一樣。
// 全局變量 private MediaSample mediaSample; private BufferedImage img, formatImg; private int prevSampNum; private int sampNum = 0; private int numCycles = 0; private int numSkips = 0; // 在getFrame()函數(shù)中, //以秒為單位取得從QTSnapper1開始以來的時(shí)間 double currTime =((double)(System.currentTimeMillis()-startTime))/1000.0; // 使用視頻的時(shí)間刻度 int videoCurrTime=((int)(currTime*timeScale)) % duration; try { // 備份前一個樣本號 prevSampNum = sampNum; //計(jì)算新的樣本號 sampNum = vidMedia.timeToSampleNum(videoCurrTime).sampleNum; //如果沒有樣本變化,則不生成一幅新的圖像 if (sampNum == prevSampNum) return formatImg; if (sampNum < prevSampNum) numCycles++; //動畫剛剛開始播放 // 記下跳過的幀號 int skipSize = sampNum - (prevSampNum+1); if (skipSize > 0) //跳過的幀 numSkips += skipSize; //取得從樣本號的時(shí)間開始的一個樣本 TimeInfo ti =vidMedia.sampleNumToMediaTime(sampNum); mediaSample = vidMedia.getSample(0,ti.time,1); getFrame()以秒為單位計(jì)算出當(dāng)前的時(shí)間,從QTSnapper1的啟動開始計(jì)算: double currTime =((double)(System.currentTimeMillis()-startTime))/1000.0; |
每一段QuickTime媒體都有它自己的時(shí)間刻度-ts,這樣,一個時(shí)間單位就是1/ts秒。用時(shí)間刻度常數(shù)乘以currTime的值即得到在動畫時(shí)間單位中的當(dāng)前時(shí)間值:
int videoCurrTime=((int)(currTime*timeScale)) % duration; |
該時(shí)間值被用媒體持續(xù)時(shí)間為模作模運(yùn)算加以修正,允許動畫在當(dāng)前時(shí)間到達(dá)動畫末尾時(shí)重復(fù)播放。
這個時(shí)間值被通過調(diào)用Media的timeToSampleNum()方法映射到一個樣本索引號:
sampNum=vidMedia.timeToSampleNum(videoCurrTime).sampleNum; |
前面使用的樣本號存儲在變量prevSampNum中,這就允許進(jìn)行一些測試和計(jì)算。
如果"新的"樣本號與前一個相同,那么就沒有必要費(fèi)勁地把這個樣本轉(zhuǎn)化成一個BufferedImage;getFrame()方法能夠返回存在的formatImg參照。
如果新的樣本號小于前一個相同,這意味著動畫已經(jīng)循環(huán)播放了,動畫最開始的一幀將被播出。該循環(huán)通過增加變量numCycles的值來登記。
如果新的樣本號大于前一個幀號加1,那就記下跳過的樣本號。
收尾處理
stopMovie()打印出FPS值并關(guān)閉QuickTime會話,其實(shí)現(xiàn)方式同QTSnapper中的stopMovie()方法很相近。該函數(shù)還給出一些額外信息:
long totalFrames =(numCycles * numSamples) + sampNum; //報(bào)告跳過幀的百分比數(shù) double skipPerCent=(double)(numSkips*100)/totalFrames; System.out.println("Percentage frames skipped: "+ frameDf.format(skipPerCent) + "%"); //"表面上的"FPS (AFPS) double appFrameRate = ((double) totalFrames * 1000.0) / duration; System.out.println("AFPS: "+frameDf.format(appFrameRate));//1dp |
appFrameRate代表了"表面上的"幀速率,該值是從QTSnapper1的創(chuàng)建開始算起,直到迭代結(jié)束得到的總幀數(shù)。所謂"表面上的",其意指并非所有的樣本必須被著色到屏幕上。
應(yīng)用程序能運(yùn)行嗎?是否運(yùn)行良好?
以QTSnapper1取代QTSnapper,原來慢速的動畫(如圖1所示)現(xiàn)在的播放加快了。在播放快結(jié)束時(shí),程序報(bào)出的表面上的幀速率數(shù)字達(dá)到31 FPS,實(shí)際的幀速率大約在16 FPS,跳過的幀數(shù)將近達(dá)到總幀數(shù)的50%。驚人的是,這么巨大數(shù)目的幀丟失在屏幕上的影響看上去并不明顯。
對于另外一些小型動畫來說,速度的提升并不那么引人注目;跳過的幀數(shù)所占百分比大約在5-10%。
不幸的是,存在兩個問題:當(dāng)跳躍幀時(shí)可能出現(xiàn)混雜的像素;對可以跳過的幀號缺乏有效的控制。
混雜的像素
這種效果如圖6所示,不準(zhǔn)確的像素使用了來自于視頻中前一階段的值。任何時(shí)候當(dāng)QTSnapper1跳過一個動畫幀時(shí),下一幅被檢索到的幀將會包含一些混雜的像素。其效果可以在圖6中看到。這些不準(zhǔn)確的像素使用了來自于視頻中前一階段的值。
![]() 圖 6. 部分混雜的圖像 |
借助于來自quicktime-java 郵件列表的人們(特別感謝George Birbilis和Dean Perry)的幫助,我找到了一個解決辦法。
該問題是,我原先所有的動畫樣本都使用了temporal compression壓縮算法,該壓縮算法主要是利用了相鄰視頻幀的相似性。如果兩個連續(xù)的幀有相同的背景,那么就沒有必要再次存儲背景了,而是僅存起這兩幀的不同處即可。
這項(xiàng)技術(shù),幾乎為所有流行的視頻格式所應(yīng)用,意味著從幀中提取的一幅圖像將依賴于該幀,也潛在地依賴于前面幾個幀。
Temporal decompression解壓算法在一個QuickTime DSequence對象中完成,該對象又為我自己的writeToBufferedImage()方法中所用。在Dsequence的構(gòu)造器指定,在解壓過程中QuickTime應(yīng)該使用脫屏圖像緩沖區(qū)進(jìn)行操作。
幀中的圖像被寫向緩沖區(qū),在此其又與早期的幀數(shù)據(jù)相結(jié)合。結(jié)果圖像被傳遞到轉(zhuǎn)換過程的下一步。
這種方式在QTSnapper1以順序方式(不進(jìn)行幀跳過,例如:1,2,3,4)解壓樣本時(shí)運(yùn)行良好,但是在有跳幀的情況下報(bào)出錯誤。例如,當(dāng)QTSnapper1跳過第5、6幀,然后解壓第7幀,情況會怎樣呢?該幀被寫向一個QuickTime 圖像緩沖區(qū),然后又與早期的幀數(shù)據(jù)相結(jié)合。不幸的是,第5、6幀中的數(shù)據(jù)丟失了,因此結(jié)果圖像是不正確的。
簡單地說,圖像中進(jìn)來的混雜像素是由于動畫中使用了temporal compression壓縮算法的結(jié)果。一種選擇是使用spatial compression壓縮算法,這種算法獨(dú)立地壓縮每一幀數(shù)據(jù),就象對單一圖像進(jìn)行JPEG或者GIF壓縮的方式很相似。這種算法意味著,用于解壓一幀的所有信息均來自于該幀本身;沒有必要對前面的幀進(jìn)行檢查。
QuickTime MOV動畫格式支持一種稱作Motion-JPEG (M-JPEG)的a spatial compression壓縮方式。我使用 QuickTime 6 Pro中的輸出工具,選取"M-JPEG A codec"編碼方式后,把圖1中的樣本保存成一種MOV文件。當(dāng)Movie3D應(yīng)用程序播放該動畫時(shí),沒有混雜現(xiàn)象發(fā)生。
限制幀跳躍
關(guān)于QTSnapper1的另外一個問題是,getFrame()方法并不限制能夠跳過的幀數(shù)。在我的測試過程中,跳過幀數(shù)的上限是3時(shí),其效果并不顯著。但是,如果getFrame()方法中使用一個較大的樣本(例如,較大的尺寸和分辨率)加以轉(zhuǎn)換,那么,代碼運(yùn)行速度的明顯變慢將會由更多的幀跳越數(shù)所補(bǔ)償,只是動畫質(zhì)量非常明顯地變差。
4. 盡量加快圖像的生成
上面在QTSnapper和QTSnapper1中使用的sample-to-BufferedImage轉(zhuǎn)換方法(writeToBufferedImage())來源于Chris W. Johnson的一個例程。是否還存在更快的從樣本中提取圖像的方法?
關(guān)于QTJ的標(biāo)準(zhǔn)參考書是《QuickTime for Java: A Developer’s Notebook》(作者Chris Adamson,O’Reilly,出版時(shí)間2005.1)。本書的第五章介紹了QuickDraw內(nèi)容,其中包含一個例子ConvertToJavaImageBetter.java,該例說明了怎樣把一個樣本抓取成一個PICT圖像,然后把它轉(zhuǎn)化成一個Java Image對象。你也可以在quicktime-java郵件列表處發(fā)現(xiàn)這個例子。
其中的轉(zhuǎn)化方法并不直截易懂,這依賴于加在PICT對象前面的一個虛構(gòu)的512字節(jié)的頭部,這樣該對象就可以被QuickTime 版本的ImageProducer當(dāng)作一個PICT文件對待。
我借用了Adamson的代碼作為我的另一個稱作QTSnapper2的Snapper類的基本代碼組成。該類負(fù)責(zé)一個沒有幀跳躍的幀系列的著色,其工作方式同QTSnapper,但是使用了PICT-to-Image轉(zhuǎn)換方法。
對于一些小的動畫,QTSnapper2的性能與QTSnapper相差無幾,但是對于一些稍微大些的如圖1中的動畫,與QTSnapper的16 FPS幀速率相比其平均幀速率下降到大約9 FPS。也就是說,基于PICT的轉(zhuǎn)換技術(shù)慢于Johnson所用的技術(shù)。