`
love19820823
  • 浏览: 935122 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

写在前面的话:


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


00


事实上,较之最初的 RGB 模式, ARGB 仅仅增加了一个名为 Alpha 的色彩通道。这是一个 8 位的灰度通道,用 256 级灰度来记录图像中的透明度信息,定义透明、不透明和半透明区域。通俗的说,你的 ARGB 图像是否透明,与底层图像的遮挡关系如何,都将由 Alpha 这个参数所决定。

00


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) ,则意味着全透明。


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


00


二、 新生代的 PulpCore

项目地址: http://www.interactivepulp.com/pulpcore/

事实上, PulpCore 在国外的 Java 圈中也算颇有名气,甚至连某位 JavaFX 开发者都曾以它和自己的项目作过比较。如果有朋友泡过 http://www.javagaming.org/ ,想必应该知道,如果你在该论坛中寻求 Java 游戏框架,那么 3D 方面的优先推荐必然是 JME 2D 方面的优先推荐绝对是 Slick2D ,至于网页游戏开发方面,则必属 PulpCore 无疑。

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

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


00


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 的替代封装,以方便后文讲解分析其渲染方式之用。


00


示例源码:

另外笔者还要补充一点,那就是 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 好赚……



分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics