浅谈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示例:

 

  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开发者都曾以它和自己的项目作过比较。如果有朋友泡过http://www.javagaming.org/,想必应该知道,如果你在该论坛中寻求Java游戏框架,那么3D方面的优先推荐必然是JME2D方面的优先推荐绝对是Slick2D,至于网页游戏开发方面,则必属PulpCore无疑。

 

在以OpenGL为绝对主流的javagaming上,一款以标准Java2D开发的框架,居然会受到如此推崇,PulpCore的技术价值我们可想而知。

 

下图为PulpCore提供的应用示例:


00


PS:虽然PulpCore所提供的示例多为小游戏,但该作者曾反复强调,PulpCore是一个开源的2D渲染和动画处理框架。

 

Processing一样,启动PulpCoreCoreApplet继承自Applet,所以PulpCore依旧属于Applet实现,也就是默认情况下只能运行于网页之上。但相对于标准Applet应用,PulpCore却做了更多的优化,尤其注重用户体验与动画效果。应该说,Pulpcore是目前为止笔者所见过的,在不损失图像色彩的情况下最高效的Java2D解决方案。

 

关于图像渲染部分,PulpCore中有对应于标准Java2DGraphics类,名为

猜你喜欢

转载自blog.csdn.net/aabbyyz/article/details/84076513