浅谈JavaSE性能优化(1)——BufferedImage与像素级渲染

来源:互联网 发布:淘宝店铺的描述怎么写 编辑:程序博客网 时间:2024/04/28 19:23

写在前面的话:


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

 


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类,名为CoreGraphics。其中对像素级操作进行了必要的封装,也基本参照标准Java2D API命名。(PS:具体留待下节讲解,目前请自行参考其源码)不过,或许是方便模块化管理的缘故,CoreGraphics默认情况下并不对外开放,而被统一封装在PulpCore所提供的各种精灵类里。

 

如果您想要获得CoreGraphics进行修改,要么请重载Spritedraw(需要super.draw一下,否则会覆盖到基础操作) ,要么请在Scene2D中重载drawScene(需要super.drawScene一下,否则会覆盖到基础操作),PulpCore并没有直接提供给您。对于仅想进行简单图形绘制的用户而言,这不得不说是一个小小的不足。

 

另外,虽然PulpCore也有对应于FontCoreFont类,但相比于Processing的字体绘制方案,它明显寒酸了很多。

 

实际上,PulpCore中的CoreFont只是一个分图管理器,由用户导入一张由英文字母及各种符号组成的图像,而CoreFont负责分配不同的图像对应不同的字母绘制。这意味着,如果您不自行扩充其CoreFont部分,那么PulpCore将绝对无法支持中文输入及显示。(PS:目前来说,最偷懒的方法就是将Processing中的PFonttext部分直接“移植”到CoreGraphics中使用。毕竟两者都是操作像素绘制图像,很好copy……)

 

针对PulpCoreCoreGraphics,笔者也提供了一个LGame的替代封装,以方便后文讲解分析其渲染方式之用。


00


示例源码:

  

 

另外笔者还要补充一点,那就是PulpCore虽然提供了较为完善的“脏绘”机制,却必须和Sprite一起使用才能看到效果(被封装到了Spritedraw函数里,所以单就渲染速度而言,在PulpCore中使用精灵绘图反而比不用更快)。

 

从下文开始,笔者将以PulpCore为基础,逐步讲解Java像素级渲染框架的设计与实现。



以下为以PulpCore与Processing进行渲染的LGame实验工程:

 

http://loon-simple.googlecode.com/files/Pixels-LGame.7z


 

——————————————————————


心理学上有一个名词叫做The Halo Effect ,也就是俗称的晕轮效应或者说“刻板印象”。在这种心理现象影响下,很多人往往会将某种事物的“第一印象”当作终身的准则,而无视其实际究竟是怎样的。

 

其实对于Java桌面或网页应用而言,性能上的问题早就已经算不得什么问题。只要人们稍微留心一下,就会发现Java在游戏开发方面,至少在网页游戏方面完全可以比某些东西作的更好,更强,更复杂,更快捷,也更稳定。真正关键的,反倒是那些听信了流言蜚语的人们对于Java性能上的误解,以及食古不化的偏见,才是真正制约Java发展的拦路虎。

 

所谓积重难返,要想扭转这种顽固偏见,只凭小弟一人是绝对不足够的,还要靠各位Java同仁的努力。

 

都说“荒田无人耕,耕开有人争”,可都等着别人耕田,毕竟太慢,始终没有自己动手那么快捷。毕竟只有将Java做大做强,各位同仁才能有更多的出路,更好的待遇,以及更多的Money好赚……



原创粉丝点击