1. 程式人生 > >淺談JavaSE效能優化(1)——BufferedImage與畫素級渲染

淺談JavaSE效能優化(1)——BufferedImage與畫素級渲染

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

寫在前面的話:


JAVA應用結構簡單,易於編寫,能夠輕易完成高強度的複雜互動,並且安全性高,穩定性強,免費資源豐富,網路功能強大,擁有近乎完美的多執行緒機制。有必要的前提下,Java程式設計師甚至可以使用

JNI直接與本地環境溝通,從而繞過虛擬機器的效能制約。

 

JAVA應用的跨平臺特性,更(理論上)讓其可以運行於任何系統和平臺之上,最大限度的增加了程式的通用可能。

 

從本質上講,無論你以Java開發桌面應用也好,網頁應用也罷,其實並沒有明顯的界線存在。究其根本,無非是使用Applet/JApplet/JavaFX當做容器,抑或AWT/Swing/SWT當作容器的區別罷了。

 

快捷、靈活、通用、穩定,以上這些優勢,原本足以讓JAVA將成為未來網頁遊戲乃至中小型桌面遊戲開發的主流語言之一。

 

然而,Java的執行效率問題,似乎卻成了這一些美好前景的絆腳石。更直接的說,有一些人武斷的認為,

Java“緩慢”的執行速度,讓它根本不適合作為遊戲客戶端之用。

 

即便自JDK1.6Java的圖形渲染能力已經有了顯著提升,即便國外像RuneScape之類的Java3D網頁遊戲已經上線盈利很多年(PS:順便鄙視下Jagex最近對RuneScape作的人物屬性調整……),即便連NetBeans的執行速度都已經變得能同普通桌面程式不遑多讓。但是,某些自2004年後或許從未接觸過新技術的傢伙,依舊樂此不疲的散佈著有關Java效能的流言蜚語。

 

在某些落伍人士眼裡,Java如同洪水猛獸,又好像是他們天生的對頭。他們甚至寧願選擇某些行將就木的技術,他們甚至寧願將某些只適合做低成本動畫的東西視為命根,他們甚至寧願花大力氣去處理那些因為不支援實際多執行緒、

CPU佔用過高、硬體加速不到位、資源回收異常等等問題而引發的致命BUG,也不願意去多瞭解一下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表示),將RedGreenBlue三種色彩加以混合,創造出唯一的色彩點並繪製到計算機之上。而這個色彩點,也就是所謂的畫素。因為在RGB24RedGreenBlue三者都被分配有一個0~255的強度值,所以該RGB模式的極限機能就是256*256*256,即至多可以顯示出16777216種顏色。

 

PS:關於16位的RGB565Java2D中表示為TYPE_USHORT_565_RGB)以及RGB555Java2D中表示為TYPE_USHORT_555_RGB)會在以後章節中涉及,大家此刻只要知道,使用24位以下的圖形處理模式,在顯示速度上雖然會有提高,視覺效果上卻必然會有損失就可以了。

 

也許有網友會感嘆。哇!16777216種顏色,這麼多?難道都能用上嗎?!

 

沒錯,16777216種顏色確實很多;事實上,這已非常接近於人類肉眼所能觀察到的顏色數目極限, 所以我們又將它稱之為真彩色。然而,人類的欲求卻是無止境的,即便能夠展現出16777216種顏色的RGB真彩模式,依舊有人嫌棄它的效果太差。

 

否則,在您計算機“顏色質量”一欄中,或許就不會再有32位這種“多餘”的選擇了。

 

正是因為人類天性的貪婪,當今2D3D圖形渲染中最為常見的ARGB模式,也就是32位真彩模式才會應運而生。

 

ARGB模式:

 

您問什麼是ARGB?其實,它就是個穿了Alpha通道馬甲的RGB


00


事實上,較之最初的RGB模式,ARGB僅僅增加了一個名為Alpha的色彩通道。這是一個8位的灰度通道,用256級灰度來記錄影象中的透明度資訊,定義透明、不透明和半透明區域。通俗的說,你的ARGB影象是否透明,與底層影象的遮擋關係如何,都將由Alpha這個引數所決定。

 

00


Java2D中, TYPE_INT_ARGB象徵著32 位十六進位制數的ARGB色彩模式。

 

將“32 位十六進位制數”的概念具象化後,也就是四對十六進位制數字的序列。每個十六進位制對定義四個顏色通道,即RedGreenBlueAlpha中每個顏色通道的強度,全以範圍介於 0 255 之間的十進位制數的十六進位制表示法。(在16進製表示中,FF 是指全強度 ,最高的25500 是指通道中無顏色,最低為0

 

正如大家都知道的那樣, 由於顏色值長度需要兩位數字, 因此您需要填充一個通道, 例如用 01 代替 1,這樣才可確保十六進位制數中始終具有八個數字。還應確保指定十六進位制數字首 0x,這樣才能被Java識別為16進位制。

 

例如,白色 (全強度) 用十六進位制記數法表示為: 0xFFFFFFFF。而黑色正好相反;它在紅色、綠色和藍色中的任何一個通道中都無顏色,結果就成了: 0xFF000000。請注意, Alpha 通道中的全強度意味著沒有 Alpha (FF),也就是不透明, 而無強度 (00),則意味著全透明。


00


利用ARGB模式,我們可以輕易的創建出一些RGB所無法實現的豔麗影象,完成一些RGB所無法企及的繽紛效果。應該說,如果您只是想製作一個讓人可以入目的畫面,那麼普通的RGB模式已然遊刃有餘,但如果您想百尺竿頭更進一步,製作出一些讓人心曠神怡的視覺盛宴,那就非ARGB不可。而一旦您開始使用ARGB,就與AlphaRedGreenBlue這四層色彩通道留下了不解之緣。

 

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物件獲取畫素集合。

 

//建立一個640x480BufferedImage,設定渲染模式為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色彩模型,並填充相應的RGB掩碼

DirectColorModel directColorModel = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF);

// 以下為32RGB色彩模型

// 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);

//SampleModelDataBuffer生成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

 

專案地址:http://processing.org/

 

這是一套完整的,開源的,兼顧2D3D方面的Java渲染元件。事實上,Processing在針對Java2D效能優化上的意義並不太大,因為它本來就不是為了解決效能問題而出現的。

 

Processing所做的,更多的是一種效果優化,一種對 Java 語言的延伸。它希望人們能利用它對Java的擴充,以簡單高效的方式實現絢麗奪目的圖形效果。應該說,Processing Java 的語法簡化並將其運算結果感官化,讓使用者能很快享有聲光兼備的互動式多媒體作品。

 

由於Processing運行於PApplet之上,而 PApplet 繼承自Applet 。也就是說原本的 Processing 也是一種小程式,如果我們要將它應用在網頁環境之外,要們就將PApplet插入到Frame/JFrame當中,要麼就將其改寫。

 

為了未來的演示更加方便,筆者選擇了改寫的道路,將其PGraphics渲染層直接封裝。以下,是一個已經替換為Processing渲染的LGame示例:

 

[java] view plain copy print ?
  1. public class ProcessingBall extends Screen {  
  2.    
  3.     class Ball {  
  4.    
  5.        float x;  
  6.    
  7.        float y;  
  8.    
  9.        float speed;  
  10.    
  11.        float gravity;  
  12.    
  13.        float w;  
  14.    
  15.        float life = 255;  
  16.    
  17.        Ball(float tempX, float tempY, float tempW) {  
  18.            x = tempX;  
  19.            y = tempY;  
  20.            w = tempW;  
  21.            speed = 0;  
  22.            gravity = 0.1f;  
  23.        }  
  24.    
  25.        void move() {  
  26.            speed = speed + gravity;  
  27.            y = y + speed;  
  28.            if (y > getHeight()) {  
  29.               speed = speed * -0.8f;  
  30.               y = getHeight();  
  31.            }  
  32.        }  
  33.    
  34.        boolean finished() {  
  35.            life--;  
  36.            if (life < 0) {  
  37.               return true;  
  38.            } else {  
  39.               return false;  
  40.            }  
  41.        }  
  42.    
  43.        void display(LPGraphics g) {  
  44.            g.fill(0, life);  
  45.            g.ellipse(x, y, w, w);  
  46.        }  
  47.     }  
  48.    
  49.     private ArrayList balls;  
  50.    
  51.     private int ballWidth = 48;  
  52.    
  53.     PImage image=Utils.loadImage("system/image/logo.png");  
  54.      
  55.     public ProcessingBall() {  
  56.        balls = new ArrayList();  
  57.        balls.add(new Ball(getWidth() / 20, ballWidth));  
  58.     }  
  59.    
  60.     public void draw(LPGraphics g) {  
  61.        g.background(255);  
  62.        for (int i = balls.size() - 1; i >= 0; i--) {  
  63.            Ball ball = (Ball) balls.get(i);  
  64.            ball.move();  
  65.            ball.display(g);  
  66.            if (ball.finished()) {  
  67.               balls.remove(i);  
  68.            }  
  69.        }  
  70.     }  
  71.    
  72.     public void leftClick(MouseEvent e) {  
  73.        balls.add(new Ball(getMouseX(), getMouseY(), ballWidth));  
  74.     }  
  75.    
  76.     public void middleClick(MouseEvent e) {  
  77.    
  78.     }  
  79.    
  80.     public void rightClick(MouseEvent e) {  
  81.    
  82.     }  
  83.    
  84.     public void onKey(KeyEvent e) {  
  85.    
  86.     }  
  87.    
  88.     public void onKeyUp(KeyEvent e) {  
  89.    
  90.     }  
  91.    
  92.     public static void main(String[] args) {  
  93.        GameScene frame = new GameScene("球體下落"480360);  
  94.        Deploy deploy = frame.getDeploy();  
  95.        deploy.setScreen(new ProcessingBall());  
  96.        deploy.setShowFPS(true);  
  97.        deploy.setFPS(100);  
  98.        deploy.mainLoop();  
  99.        frame.showFrame();  
  100.     }  
  101.    
  102. }  
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();    } }


00


二、新生代的PulpCore

 

專案地址:http://www.interactivepulp.com/pulpcore/

 

事實上,PulpCore在國外的Java圈中也算頗有名氣,甚至連某位JavaFX開發者都曾以它和自己的專案作過比較。如果有朋友泡過