淺談JavaSE效能優化(1)——BufferedImage與畫素級渲染
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
寫在前面的話:
JAVA應用結構簡單,易於編寫,能夠輕易完成高強度的複雜互動,並且安全性高,穩定性強,免費資源豐富,網路功能強大,擁有近乎完美的多執行緒機制。有必要的前提下,Java程式設計師甚至可以使用
而JAVA應用的跨平臺特性,更(理論上)讓其可以運行於任何系統和平臺之上,最大限度的增加了程式的通用可能。
從本質上講,無論你以Java開發桌面應用也好,網頁應用也罷,其實並沒有明顯的界線存在。究其根本,無非是使用Applet/JApplet/JavaFX當做容器,抑或AWT/Swing/SWT當作容器的區別罷了。
快捷、靈活、通用、穩定,以上這些優勢,原本足以讓JAVA將成為未來網頁遊戲乃至中小型桌面遊戲開發的主流語言之一。
然而,Java的執行效率問題,似乎卻成了這一些美好前景的絆腳石。更直接的說,有一些人武斷的認為,
即便自JDK1.6起Java的圖形渲染能力已經有了顯著提升,即便國外像RuneScape之類的Java3D網頁遊戲已經上線盈利很多年(PS:順便鄙視下Jagex最近對RuneScape作的人物屬性調整……),即便連NetBeans的執行速度都已經變得能同普通桌面程式不遑多讓。但是,某些自2004年後或許從未接觸過新技術的傢伙,依舊樂此不疲的散佈著有關Java效能的流言蜚語。
在某些落伍人士眼裡,Java如同洪水猛獸,又好像是他們天生的對頭。他們甚至寧願選擇某些行將就木的技術,他們甚至寧願將某些只適合做低成本動畫的東西視為命根,他們甚至寧願花大力氣去處理那些因為不支援實際多執行緒、
不得不說,這即是Java的遺憾,更是某些落伍人士僱主及其公司,乃至整個遊戲產業的遺憾。
當然,一味的指責他人,就成了抱怨,勢必會犯“有嘴說別人,沒嘴說自己”的民族通病。事實上,人們對於Java效能方面之所以會產生誤解,除了旁人的傲慢與偏見外,自然也同Java自身的發展歷程密不可分(具體原因我在其它的博文中已經闡述過很多次,此處不再贅述)。
但總體上講,除了原Sun公司本身的不作為,以及Java偏向企業級開發,偏向伺服器端開發的大環境影響外。Java進行遊戲開發,或者說桌面開發的最大缺陷,就在於其圖形開發方面,特別是有關於渲染優化方面,乃至整個Java遊戲開發領域的書籍資料都嚴重匱乏。沒錯,相比浩如煙海的Java伺服器端技術資料而言,Java遊戲開發方面的資源鳳毛麟角。在某個黑暗時期中,甚至連Java版的貪食蛇、俄羅斯方塊、超級馬里奧之類的資源都會被人視為經典。
不客氣地說,如果憑那些東西就想讓人樹立對於Java遊戲開發的信心,就算不等於痴人說夢,至少也是難於登天了。
而如果想要解決這個問題,那麼更多的示例,以及更多的技術文章將必不可少。為此,筆者才會產生創作此係列博文的意願,唯恨椽筆拙文,權作引玉之磚。
正文:BufferedImage與畫素級渲染
常有人說Java圖形渲染很慢?嗯,相對C/C++而言,Java2D固有的影象處理能力確實有待提高。
但是,這也僅僅侷限於對比C/C++應用而言。
如果您是以其它什麼東西與之比較,卻得出Java渲染很慢的結論。那麼,或者並不是出自Java本身的原因,而在於您並沒能搞清楚該怎樣正確的使用Java繪圖。
況且,即便是相對於C/C++而談,Java也並非相差到難以望其項背的地步。相對於某些行將就木的技術,至少我們除了異常積極的自行修改JRE,或者極端消極的等待JRE官方更新以外,還有使用OpenGL或者畫素級優化這兩條道路可走。
在本節當中,我們就先談點基礎的,來說說Java渲染的畫素級優化吧。
畫素與RGB:
畫素是什麼?簡單的講,畫素就是色彩,畫素是系統能夠在計算機螢幕上顯示的最小染色點。越高位的畫素,其擁有的色板也就越豐富,越能表達顏色的真實感。
眾所周知,影象是畫素的複合,看似絢麗的形象,也無外是一個個肉眼難以分辨的細微顆粒集合罷了。
比如,在一些常見的Java影象處理中,我們經常會用到所謂的RGB24模式(24位三原色模式,在Java2D中以TYPE_INT_RGB表示),將Red,Green,Blue三種色彩加以混合,創造出唯一的色彩點並繪製到計算機之上。而這個色彩點,也就是所謂的畫素。因為在RGB24中Red,Green,Blue三者都被分配有一個0~255的強度值,所以該RGB模式的極限機能就是256*256*256,即至多可以顯示出16777216種顏色。
PS:關於16位的RGB565(Java2D中表示為TYPE_USHORT_565_RGB)以及RGB555(Java2D中表示為TYPE_USHORT_555_RGB)會在以後章節中涉及,大家此刻只要知道,使用24位以下的圖形處理模式,在顯示速度上雖然會有提高,視覺效果上卻必然會有損失就可以了。
也許有網友會感嘆。哇!16777216種顏色,這麼多?難道都能用上嗎?!
沒錯,16777216種顏色確實很多;事實上,這已非常接近於人類肉眼所能觀察到的顏色數目極限, 所以我們又將它稱之為真彩色。然而,人類的欲求卻是無止境的,即便能夠展現出16777216種顏色的RGB真彩模式,依舊有人嫌棄它的效果太差。
否則,在您計算機“顏色質量”一欄中,或許就不會再有32位這種“多餘”的選擇了。
正是因為人類天性的貪婪,當今2D、3D圖形渲染中最為常見的ARGB模式,也就是32位真彩模式才會應運而生。
ARGB模式:
您問什麼是ARGB?其實,它就是個穿了Alpha通道馬甲的RGB。
事實上,較之最初的RGB模式,ARGB僅僅增加了一個名為Alpha的色彩通道。這是一個8位的灰度通道,用256級灰度來記錄影象中的透明度資訊,定義透明、不透明和半透明區域。通俗的說,你的ARGB影象是否透明,與底層影象的遮擋關係如何,都將由Alpha這個引數所決定。
在 Java2D中, TYPE_INT_ARGB象徵著32 位十六進位制數的ARGB色彩模式。
將“32 位十六進位制數”的概念具象化後,也就是四對十六進位制數字的序列。每個十六進位制對定義四個顏色通道,即Red、Green、Blue和Alpha中每個顏色通道的強度,全以範圍介於 0 到 255 之間的十進位制數的十六進位制表示法。(在16進製表示中,FF 是指全強度 ,最高的255。00 是指通道中無顏色,最低為0)
正如大家都知道的那樣, 由於顏色值長度需要兩位數字, 因此您需要填充一個通道, 例如用 01 代替 1,這樣才可確保十六進位制數中始終具有八個數字。還應確保指定十六進位制數字首 0x,這樣才能被Java識別為16進位制。
例如,白色 (全強度) 用十六進位制記數法表示為: 0xFFFFFFFF。而黑色正好相反;它在紅色、綠色和藍色中的任何一個通道中都無顏色,結果就成了: 0xFF000000。請注意, Alpha 通道中的全強度意味著沒有 Alpha (FF),也就是不透明, 而無強度 (00),則意味著全透明。
利用ARGB模式,我們可以輕易的創建出一些RGB所無法實現的豔麗影象,完成一些RGB所無法企及的繽紛效果。應該說,如果您只是想製作一個讓人可以入目的畫面,那麼普通的RGB模式已然遊刃有餘,但如果您想百尺竿頭更進一步,製作出一些讓人心曠神怡的視覺盛宴,那就非ARGB不可。而一旦您開始使用ARGB,就與Alpha、Red、Green、Blue這四層色彩通道留下了不解之緣。
在Java中獲得ARGB畫素的方法如下:
public static int getARGB(int r, int g, int b, int alpha) {
return (alpha << 24) | (r << 16) | (g << 8) | b;
}
關於BufferedImage:
當我們需要使用畫素級操作,當我們需要設定針對不同影象的不同色彩模式時,最直接有效的方法,就是使用BufferedImage。
事實上,就像深入優化Flash渲染必須利用BitmapData一樣,沒有對BufferedImage的相關了解,提高Java2D效能根本無從談起,甚至不能說你會用Java2D。
當您想要建立BufferedImage,並對其中畫素進行直接操作時,大體上有三種方式可選:
1、直接建立BufferedImage,匯出DataBufferInt物件獲取畫素集合。
//建立一個640x480的BufferedImage,設定渲染模式為ARGB
BufferedImage image = new BufferedImage(640, 480,
BufferedImage.TYPE_INT_ARGB);
//獲得當前BufferedImage的影象資料儲存器,並轉為DataBufferInt
DataBufferInt dataBuffer = ((DataBufferInt) image.getRaster()
.getDataBuffer());
//獲得對應BufferedImage的畫素陣列
int[] pixels = dataBuffer.getData();
2、以int[]生成WritableRaster,以WritableRaster產生BufferedImage。
//設定BufferedImage的寬與高
int width = 640, height = 480;
int size = width * height;
//建立陣列,用以儲存對應BufferedImage的畫素集合
int[] pixels = new int[size];
//以指定陣列創建出指定大小的DataBuffer
DataBuffer dataBuffer = new DataBufferInt(pixels, size);
//建立一個WritableRaster物件,用以管理光柵
WritableRaster raster = Raster.createPackedRaster(dataBuffer, width, height,width, new int[] { 0xFF0000, 0xFF00, 0xFF }, null);
//建立一個24位的RGB色彩模型,並填充相應的R、G、B掩碼
DirectColorModel directColorModel = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF);
// 以下為32位RGB色彩模型
// DirectColorModel directColorModel = new DirectColorModel(32, 0xFF000000, 0xFF0000, 0xFF00, 0xFF);
//生成BufferedImage,預設Alpha,無配置
BufferedImage image = new BufferedImage(directColorModel, raster, true, null);
3、與方法2基本相同,唯一差別在於使用了SampleModel
int width = 640, height = 480;
int size = width * height;
int[] pixels = new int[size];
// 24位色彩模型
DirectColorModel directColorModel = new DirectColorModel(24, 0xFF0000,
0xFF00, 0xFF);
// 以SinglePixelPackedSampleModel構建畫素包
SampleModel sample = new SinglePixelPackedSampleModel(
DataBuffer.TYPE_INT, width, height, new int[] { 0xFF0000,
0xFF00, 0xFF });
//生成DataBuffer
DataBuffer dataBuffer = new DataBufferInt(pixels, size);
//以SampleModel及DataBuffer生成WritableRaster
WritableRaster raster = Raster.createWritableRaster(sample, dataBuffer,
new Point(0, 0));
//生成BufferedImage
BufferedImage image = new BufferedImage(directColorModel, raster, true, null);
實際上,雖然表面上有所不同,但無論您採用以上何種方式獲得BufferedImage及其對應的畫素集合(PS:此處並非一定要獲得畫素的int[]形式,如short[]、byte[]等各式亦可,請根據實際需求決定),pixels對您而言都將成為一塊儲存有影象資料的記憶體區域,針對此pixels進行的任何修改,都將被直接反饋於BufferedImage之上。
得到了畫素集合,我們又該如何將其應用到Java2D中呢?下面,我將介紹兩個畫素級Java渲染元件給大家參考。下面我們所使用到的一切操作,也都將圍繞pixels這個以int[]形式出現的陣列展開。
一、古董級的Processing
這是一套完整的,開源的,兼顧2D與3D方面的Java渲染元件。事實上,Processing在針對Java2D效能優化上的意義並不太大,因為它本來就不是為了解決效能問題而出現的。
Processing所做的,更多的是一種效果優化,一種對 Java 語言的延伸。它希望人們能利用它對Java的擴充,以簡單高效的方式實現絢麗奪目的圖形效果。應該說,Processing 將 Java 的語法簡化並將其運算結果“感官化”,讓使用者能很快享有聲光兼備的互動式多媒體作品。
由於Processing運行於PApplet之上,而 PApplet 繼承自Applet 。也就是說原本的 Processing 也是一種小程式,如果我們要將它應用在網頁環境之外,要們就將PApplet插入到Frame/JFrame當中,要麼就將其改寫。
為了未來的演示更加方便,筆者選擇了改寫的道路,將其PGraphics渲染層直接封裝。以下,是一個已經替換為Processing渲染的LGame示例:
[java] view plain copy print ?
- public class ProcessingBall extends Screen {
- class Ball {
- float x;
- float y;
- float speed;
- float gravity;
- float w;
- float life = 255;
- Ball(float tempX, float tempY, float tempW) {
- x = tempX;
- y = tempY;
- w = tempW;
- speed = 0;
- gravity = 0.1f;
- }
- void move() {
- speed = speed + gravity;
- y = y + speed;
- if (y > getHeight()) {
- speed = speed * -0.8f;
- y = getHeight();
- }
- }
- boolean finished() {
- life--;
- if (life < 0) {
- return true;
- } else {
- return false;
- }
- }
- void display(LPGraphics g) {
- g.fill(0, life);
- g.ellipse(x, y, w, w);
- }
- }
- private ArrayList balls;
- private int ballWidth = 48;
- PImage image=Utils.loadImage("system/image/logo.png");
- public ProcessingBall() {
- balls = new ArrayList();
- balls.add(new Ball(getWidth() / 2, 0, ballWidth));
- }
- public void draw(LPGraphics g) {
- g.background(255);
- for (int i = balls.size() - 1; i >= 0; i--) {
- Ball ball = (Ball) balls.get(i);
- ball.move();
- ball.display(g);
- if (ball.finished()) {
- balls.remove(i);
- }
- }
- }
- public void leftClick(MouseEvent e) {
- balls.add(new Ball(getMouseX(), getMouseY(), ballWidth));
- }
- public void middleClick(MouseEvent e) {
- }
- public void rightClick(MouseEvent e) {
- }
- public void onKey(KeyEvent e) {
- }
- public void onKeyUp(KeyEvent e) {
- }
- public static void main(String[] args) {
- GameScene frame = new GameScene("球體下落", 480, 360);
- Deploy deploy = frame.getDeploy();
- deploy.setScreen(new ProcessingBall());
- deploy.setShowFPS(true);
- deploy.setFPS(100);
- deploy.mainLoop();
- frame.showFrame();
- }
- }
二、新生代的PulpCore
專案地址:http://www.interactivepulp.com/pulpcore/
事實上,PulpCore在國外的Java圈中也算頗有名氣,甚至連某位JavaFX開發者都曾以它和自己的專案作過比較。如果有朋友泡過