浅谈JavaSE性能优化(1)——BufferedImage与像素级渲染
来源:互联网 发布:淘宝店铺的描述怎么写 编辑:程序博客网 时间:2024/04/28 19:23
写在前面的话:
JAVA应用结构简单,易于编写,能够轻易完成高强度的复杂交互,并且安全性高,稳定性强,免费资源丰富,网络功能强大,拥有近乎完美的多线程机制。有必要的前提下,Java程序员甚至可以使用JNI直接与本地环境沟通,从而绕过虚拟机的性能制约。
而JAVA应用的跨平台特性,更(理论上)让其可以运行于任何系统和平台之上,最大限度的增加了程序的通用可能。
从本质上讲,无论你以Java开发桌面应用也好,网页应用也罢,其实并没有明显的界线存在。究其根本,无非是使用Applet/JApplet/JavaFX当做容器,抑或AWT/Swing/SWT当作容器的区别罢了。
快捷、灵活、通用、稳定,以上这些优势,原本足以让JAVA将成为未来网页游戏乃至中小型桌面游戏开发的主流语言之一。
然而,Java的运行效率问题,似乎却成了这一些美好前景的绊脚石。更直接的说,有一些人武断的认为,Java“缓慢”的运行速度,让它根本不适合作为游戏客户端之用。
即便自JDK1.6起Java的图形渲染能力已经有了显著提升,即便国外像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表示),将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
项目地址:http://processing.org/
这是一套完整的,开源的,兼顾2D与3D方面的Java渲染组件。事实上,Processing在针对Java2D性能优化上的意义并不太大,因为它本来就不是为了解决性能问题而出现的。
Processing所做的,更多的是一种效果优化,一种对 Java 语言的延伸。它希望人们能利用它对Java的扩充,以简单高效的方式实现绚丽夺目的图形效果。应该说,Processing 将 Java 的语法简化并将其运算结果“感官化”,让使用者能很快享有声光兼备的交互式多媒体作品。
由于Processing运行于PApplet之上,而 PApplet 继承自Applet 。也就是说原本的 Processing 也是一种小程序,如果我们要将它应用在网页环境之外,要们就将PApplet插入到Frame/JFrame当中,要么就将其改写。
为了未来的演示更加方便,笔者选择了改写的道路,将其PGraphics渲染层直接封装。以下,是一个已经替换为Processing渲染的LGame示例:
二、新生代的PulpCore
项目地址:http://www.interactivepulp.com/pulpcore/
事实上,PulpCore在国外的Java圈中也算颇有名气,甚至连某位JavaFX开发者都曾以它和自己的项目作过比较。如果有朋友泡过http://www.javagaming.org/,想必应该知道,如果你在该论坛中寻求Java游戏框架,那么3D方面的优先推荐必然是JME,2D方面的优先推荐绝对是Slick2D,至于网页游戏开发方面,则必属PulpCore无疑。
在以OpenGL为绝对主流的javagaming上,一款以标准Java2D开发的框架,居然会受到如此推崇,PulpCore的技术价值我们可想而知。
下图为PulpCore提供的应用示例:
PS:虽然PulpCore所提供的示例多为小游戏,但该作者曾反复强调,PulpCore是一个开源的2D渲染和动画处理框架。
与Processing一样,启动PulpCore的CoreApplet继承自Applet,所以PulpCore依旧属于Applet实现,也就是默认情况下只能运行于网页之上。但相对于标准Applet应用,PulpCore却做了更多的优化,尤其注重用户体验与动画效果。应该说,Pulpcore是目前为止笔者所见过的,在不损失图像色彩的情况下最高效的Java2D解决方案。
关于图像渲染部分,PulpCore中有对应于标准Java2D的Graphics类,名为CoreGraphics。其中对像素级操作进行了必要的封装,也基本参照标准Java2D API命名。(PS:具体留待下节讲解,目前请自行参考其源码)不过,或许是方便模块化管理的缘故,CoreGraphics默认情况下并不对外开放,而被统一封装在PulpCore所提供的各种精灵类里。
如果您想要获得CoreGraphics进行修改,要么请重载Sprite的draw(需要super.draw一下,否则会覆盖到基础操作) ,要么请在Scene2D中重载drawScene(需要super.drawScene一下,否则会覆盖到基础操作),PulpCore并没有直接提供给您。对于仅想进行简单图形绘制的用户而言,这不得不说是一个小小的不足。
另外,虽然PulpCore也有对应于Font的CoreFont类,但相比于Processing的字体绘制方案,它明显寒酸了很多。
实际上,PulpCore中的CoreFont只是一个分图管理器,由用户导入一张由英文字母及各种符号组成的图像,而CoreFont负责分配不同的图像对应不同的字母绘制。这意味着,如果您不自行扩充其CoreFont部分,那么PulpCore将绝对无法支持中文输入及显示。(PS:目前来说,最偷懒的方法就是将Processing中的PFont和text部分直接“移植”到CoreGraphics中使用。毕竟两者都是操作像素绘制图像,很好copy……)
针对PulpCore的CoreGraphics,笔者也提供了一个LGame的替代封装,以方便后文讲解分析其渲染方式之用。
示例源码:
另外笔者还要补充一点,那就是PulpCore虽然提供了较为完善的“脏绘”机制,却必须和Sprite一起使用才能看到效果(被封装到了Sprite的draw函数里,所以单就渲染速度而言,在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好赚……
- 浅谈JavaSE性能优化(1)——BufferedImage与像素级渲染
- BufferedImage 与像素级渲染
- android性能优化——渲染性能
- 浅谈Unity的渲染优化(1): 性能分析和瓶颈判断(上篇)
- Android性能优化——渲染
- 浅谈前端性能优化(七)——图像优化
- 浅谈前端性能优化(八)——cookie优化
- Android性能优化——渲染、内存、电源优化
- iOS渲染机制与性能优化
- Android性能优化典范——渲染性能(Render Performance)
- 《Android应用性能优化》3——电量、渲染
- 一、Android性能优化(渲染机制、内存与GC、电量优化)带工具找问题
- BufferedImage图片渲染
- Android性能优化典范——GPU渲染(Profile GPU Rendering)
- Android UI性能优化(渲染)
- 性能优化-视图渲染
- 浅谈前端性能优化(九)——DNS解析优化
- Android-性能优化(渲染与内存)原理与工具使用两方面来分析
- 字符串详解
- 回调函数
- 作为.NET开发者你必须熟悉的几个工具
- Java 7已经完成的七大新功能预览
- 喜欢oracle这则广告
- 浅谈JavaSE性能优化(1)——BufferedImage与像素级渲染
- vc数据类型转换大全
- IT
- 保持活力
- 关于glFlush()等等
- 加班。。加班。。加班。。加班。。加班。。加班。。加班。。
- AE:IPersistStream接口进行对象保存和读取
- 一种契合生命的感悟
- 奔驰选择比亚迪合资的思考 原因解析----技术与营销 战略与战术