Java游戏开发中怎样才能获得更快的FPS?

众所周知, Java 应用的运行速度虽然不慢,却也谈不上快,以最新的 JRE1.6 表现来说,至多也就是优胜于一些纯粹的解释型语言,距离 C/C++ 等编译型的执行效率还有一定差距。
 
平心而论,如果我们使用 Java 去制作一些简单的桌面应用,那么目前 Java 组件的绘图速度勉强还能接受;但如果用 Java 来进行游戏开发,尤其是制作一些需要高 FPS 才能满足的动态效果,就必须利用技术手段对其加以优化,解决其绘图效率问题,其它才有的谈。
 
什么是 FPS
 
这里所说的 FPS ,并非指射击游戏,而是指俗称的“帧率”( Frames per Second ,缩写: FPS )或“赫兹”( Hz )。笔者在以前的博文中曾给出过专门的解释及 Java 实现示例,此处不再赘述。
 
受到人类眼球的生理结构制约,通常情况下只要我们所见画面高于每秒 16 帧,就会认为画面是连贯的,生物学上称此现象为视觉暂留,这也就是为什么电影胶片是一格一格拍摄出来,然而我们却认为它是连续的一段。当然,所谓的 16 帧也只是一个中值,并非放之四海而皆准,根据具体视觉对象不同,帧率的具体标准会有所变化。在最坏情况下,动画不低于每秒 12 帧,现代电影不低于 24 帧,动作游戏不低于 30 帧,在人眼中的画面才是连续不间断的。
 
总之, FPS 数值越高则画面流畅度便越高,越低则越显停滞,要提高 Java 游戏运行效率,那么焦点就必然要集中在如何提高程序的 FPS 之上。
 
我们该从什么地方入手,才能提高 FPS
 
Java 绘图最终要通过 native ,这是地球人都知道的事情。这意味着除非我们学习 SWT 重写本地图形接口,否则显示部分我们无法干涉;这样便决定了我们对 FPS 的优化必然要集中在本地图形系统调用之前,而非之后。也就是说,提高 Java 应用的 FPS ,还要老生常谈的从 Image Graphics 及其相关子类入手。
 
那么,对这些尽人皆知的 Java 绘图组件,我们究竟能改进那些部分呢?
 
我在不久前发布的博文《 Java 游戏开发中应始终坚持的 10 项基本原则》中,曾经就如何构建一个高效的 Java 游戏发表过一些见解,其中第 7 点“始终双缓冲游戏图像”,它不但是解决 Java 图像闪烁问题的关键所在,而且同样是提高 Java 游戏帧率的制胜法宝之一。是的, Java 绘图机能的优化与改进,关键就在于如何对其缓冲区域进行优化处理。
 
下面,我将展示三种不同的Java 缓冲绘图方式。
 
1 、采用 BufferedImage 对象进行缓冲
 
    这种方法是最简单,同时也是最常用的双缓冲构建方式,也就是构建一个 BufferedImage 缓冲当前绘图,所有 Graphics 操作在其上进行,仅在需要时才将贴图 paint 于窗体之上,使用上再简单不过,但效率如何呢?文章进行到此处时尚不得而知。
 
2 、采用 BufferStrategy 构建缓冲区
 
使用 BufferStrategy 构建缓冲能够获得系统所提供的硬件加速, Java 系统会根据本地环境选择最适合的 BufferStrategy 。要创建  BufferStrategy  ,需要使用  createBufferStrategy()  方法告诉系统你所期望的缓冲区数目(通常使用双缓冲,也就是填入“ 2 ”),并使用  getDrawGraphics()  方法在缓冲区之间进行交换,该方法返回下一个要使用的缓冲区。 BufferStrategy 最大的缺点与优点都在于其受本地图形环境影响,既不会出现很快的图形环境跑出很慢的 FPS ,也别指望很慢的图形环境跑出很快的 FPS
 
3 、完全在 BufferedImage DataBuffer 中进行图像处理
 
每个 BufferedImage 都有一个与之对应得 WritableRaster 对象( getRaster 方法获得),通过它我们获得指定 BufferedImage DataBuffer getDataBuffer 方法获得),与方法 1 类似,我们同样构建一个 BufferedImage 缓冲当前所有绘图,所有操作都在其上进行,仅在需要时才将贴图 paint 于窗体之上。但区别在于,由于 DataBuffer 可以转化为描述 BufferedImage 象素点的 int[] byte[] short[] 等数组集合,因此我们不再使用 Java 提供的 Graphics 对象,而是直接操作像素点进行所有绘图的实现。  但是,这样进行数组操作会快吗?
 
现在我们为其各自构建三个示例,尽量以比较趋同的处理流程构建,分别测算三种方法的具体效率。
 
PS :写完才突然想起,这台 2002 年买的电脑实在不适合测试 FPS -_-||| ),较新机型的 FPS 至少能达到此测试结果的 2 倍以上,特此声明……)
 
PS :以下图像素材源自成都汉森信息技术有限公司首页,特此声明)
 
以下开始将分别对三种方法分别进行绘制 1 名角色、绘制 100 名角色、绘制 1000 名角色的简单 FPS 绘图效率测算。
 
一、采用 BufferedImage 对象进行缓冲
 
代码如下:
 
1、DoubleBufferGameFrame.Java

package test1;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
 * @author chenpeng
 * @email:[email]ceponline@yahoo.com.cn[/email]
 */
public class DoubleBufferGameFrame extends Frame implements Runnable {
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private int width = 480;
 
 private int height = 360;
 
 private DoubleBufferCanvas canvas = null;
 public DoubleBufferGameFrame() {
  super("AWT标准Graphics2D使用测试");
  this.setPreferredSize(new Dimension(width + 5, height + 25));
  this.requestFocus();
  this.addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent e) {
    System.exit(0);
   }
  });
  canvas = new DoubleBufferCanvas();
  this.add(canvas);
  this.pack();
  this.setResizable(false);
  this.setLocationRelativeTo(null);
  this.setIgnoreRepaint(true);
  Thread thread = new Thread(this);
  thread.start();
 }
 public void run() {
  for (;;) {
   canvas.drawScreen();
  }
 }
 public static void main(String[] args) {
  DoubleBufferGameFrame frm = new DoubleBufferGameFrame();
  frm.setVisible(true);
 }
}
 
2、DoubleBufferCanvas.Java
 
package test1;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.Random;

public class DoubleBufferCanvas extends Canvas {
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private boolean isFPS = true;
 private boolean initFlag;
 private int frames = 0;
 private long totalTime = 0;
 private long curTime = System.currentTimeMillis();
 private long lastTime = curTime;
 private Random rand = new Random();
 private int fps;
 private Image backImage = ImageUtils.loadImage("back.gif");
 private Image hero_idle_0 = ImageUtils.loadImage("idle0_0.png",true);
 private BufferedImage bufferImage;
 private Graphics2D graphics2D;
 public DoubleBufferCanvas() {
 }
 public void update(Graphics g) {
  paint(g);
 }
 public void paint(Graphics g) {
  if (initFlag) {
   graphics2D.drawImage(backImage, 0, 0, null);
   for (int i = 0; i < 1000; ++i) {
    int x = rand.nextInt(480);
    int y = rand.nextInt(360);
    graphics2D.drawImage(hero_idle_0, x-100, y-100, null);
   }
   graphics2D.drawString(("当前FPS:" + fps).intern(), 15, 15);
   g.drawImage(bufferImage, 0, 0, null);
   g.dispose();
  }
 }
 private synchronized void initializtion() {
  bufferImage = ImageUtils.createImage(getWidth(), getHeight(), true);
  graphics2D = bufferImage.createGraphics();
  initFlag = true;
 }
 public synchronized void drawScreen() {
  getGraphics2D();
  repaint();
  if (isFPS) {
   lastTime = curTime;
   curTime = System.currentTimeMillis();
   totalTime += curTime - lastTime;
   if (totalTime > 1000) {
    totalTime -= 1000;
    fps = frames;
    frames = 0;
   }
   ++frames;
  }
  Thread.yield();
 }
 public synchronized Graphics2D getGraphics2D() {
  if (!initFlag) {
   initializtion();
  }
  return graphics2D;
 }
}
 
场景图1 、角色图1  FPS 60 - 70
 

场景图1 、角色图100  FPS 20 - 25
 

 
场景图1 、角色图1000  FPS 13 - 17
 
 
 
二、采用 BufferStrategy 构建缓冲区(双缓冲)
 

1、 BufferStrategyGameFrame.java
 
package test1;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
 * @author chenpeng
 * @email: [email]ceponline@yahoo.com.cn[/email]
 */
public class BufferStrategyGameFrame extends Frame implements Runnable {
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private BufferStrategyCanvas canvas = null;
 private int width = 480;
 
 private int height = 360;
 
 public BufferStrategyGameFrame() {
  super("AWT标准Graphics2D(BufferStrategy)使用测试");
  this.setPreferredSize(new Dimension(width + 5, height + 25));
  this.requestFocus();
  this.addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent e) {
    System.exit(0);
   }
  });
  canvas = new BufferStrategyCanvas();
  this.add(canvas);
  this.pack();
  this.setResizable(false);
  this.setLocationRelativeTo(null);
  this.setIgnoreRepaint(true);
  Thread thread = new Thread(this);
  thread.start();
 }
 public void run() {
  for (;;) {
   canvas.drawScreen();
  }
 }
 public static void main(String[] args) {
  BufferStrategyGameFrame frm = new BufferStrategyGameFrame();
  frm.setVisible(true);
 }
}
 
2、 BufferStrategyCanvas.java
 
package test1;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.util.Random;
public class BufferStrategyCanvas extends Canvas {
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private boolean isFPS = true;
 private boolean initFlag;
 private int frames = 0;
 private long totalTime = 0;
 private long curTime = System.currentTimeMillis();
 private long lastTime = curTime;
 private Random rand = new Random();
 private int fps;
 private Image backImage = ImageUtils.loadImage("back.gif");
 private Image hero_idle_0 = ImageUtils.loadImage("idle0_0.png", true);
 private BufferedImage screenImage;
 final static GraphicsEnvironment environment = GraphicsEnvironment
   .getLocalGraphicsEnvironment();
 final static GraphicsDevice graphicsDevice = environment
   .getDefaultScreenDevice();
 final static GraphicsConfiguration graphicsConfiguration = graphicsDevice
   .getDefaultConfiguration();
 private Graphics canvasGraphics = null;
 private BufferStrategy bufferImage;
 private Graphics2D graphics2d;
 public BufferStrategyCanvas() {
 }
 public void createBufferGraphics() {
  createBufferStrategy(2);
  bufferImage = getBufferStrategy();
  screenImage = graphicsConfiguration.createCompatibleImage(getWidth(),
    getHeight());
  graphics2d = screenImage.createGraphics();
 }
 public synchronized void paintScreen() {
  if (initFlag) {
   graphics2d.drawImage(backImage, 0, 0, null);
   for (int i = 0; i < 1000; ++i) {
    int x = rand.nextInt(480);
    int y = rand.nextInt(360);
    graphics2d.drawImage(hero_idle_0, x - 100, y - 100, null);
   }
   graphics2d.drawString(("当前FPS:" + fps).intern(), 15, 15);
   canvasGraphics = bufferImage.getDrawGraphics();
   canvasGraphics.drawImage(screenImage, 0, 0, null);
   bufferImage.show();
   canvasGraphics.dispose();
  }
 }
 private synchronized void initializtion() {
  createBufferGraphics();
  initFlag = true;
 }
 public synchronized void drawScreen() {
  loadGraphics();
  paintScreen();
  if (isFPS) {
   lastTime = curTime;
   curTime = System.currentTimeMillis();
   totalTime += curTime - lastTime;
   if (totalTime > 1000) {
    totalTime -= 1000;
    fps = frames;
    frames = 0;
   }
   ++frames;
  }
  Thread.yield();
 }
 public synchronized Graphics2D loadGraphics() {
  if (!initFlag) {
   initializtion();
  }
  return graphics2d;
 }
}
 
场景图1 、角色图 FPS 80 - 90
 

 
场景图1、角色图100 FPS 25 - 35
 
 
场景图1 、角色图1000  FPS 3 - 6
 
 
三、完全在 BufferedImage DataBuffer 中进行图像处理
 
实际上在 Java 网游海盗时代、 Java 网游暗影世界等网络游戏、还有 netbaens 以及其它种种不算慢的 Java 应用中,都存在大量使用 DataBuffer 进行的图像渲染;但在使用方式上,却只有暗影使用的 JGnet 引擎与常用的 Image Graphics 搭配模式较为接近,所以此示例使用了 JGnet 的部分代码构建。
 
PS :实际上汉森的 JGnet 客户端引擎目前并未开源,此部分代码得自其主页 homepage.jar 的反编译,是前天我看 csdn CTO 栏目专访汉森老总后去其首页发现的;视频中说 JGnet 有部分代码准备开源,我估计应该是 homepage.jar 里这部分,因为没有混淆且功能较少,其网站地址 [url]http://www.handseeing.com[/url] ,纯 Applet 站点 )。
 
为给不愿反编译的看客提供方便,这里我稍微对 JGnet 进行一点分析( homepage.jar 中的那部分)。
 
就代码来看, JGnet 中所有 UI 继承自 GUIWidget GUIWidget 继承自 Container ),所有 JGnet 产生的 GUI 组件也都应当继承它。 JGnet 提供了一个 GUIRoot 为底层面板,所有 GUIWidget 衍生组件都应附着于 GUIRoot 之上,另外该引擎提供有 AbstractGameClient ClientContext 接口及相关实现用以调度相关组件, JGnet GUIRoot 载体可以使用 Applet 或者 Frame
 
JGnet 中大部分资源采用 zip 打包,当然也可以读取单图, JGnet 资源加载依赖于其提供的 ImageFactory 类, ImageFactory 的操作可分为如下两步:
 
第一步是addArchive,进行此步骤时指定的zip资源会被加载,其后将read图像资源为ShortBufferImage,再根据zip中该资源对应路径缓存到daff.utill包自备的HashMap中。
 
第二步是getImage,即获得指定路径的ShortBufferImage对象,此步骤以缓存优先,如果获得失败会尝试调用loadImage方法再次加载,并缓存到daff包自备的HashMap对象中。其中loadImage方法会根据后缀判定加载的资源类型,其中jpggif或者png后缀文件会调用daff包中提供的AwtImageLoader类转化为ShortBufferImage,而非此后缀文件会直接按照ShortBufferImage格式进行加载。
 
JGnet 的图像绘制主要使用其内部提供的 ShortBufferImage ShortBufferGraphics 以及相关衍生类。 ShortBufferImage 是一个抽象类,没有父类,所有 JGnet 中图形资源都将表现为 ShortBufferImage ,绘制 ShortBufferImage 需要使用 daff.gui 包提供的专用画布 ShortBufferGraphics ,实际上 ShortBufferGraphics 是一个针对于 ShortBufferImage 的象素渲染器,内部以 short 数组形式保存有一个空白 BufferedImage DataBuffer ,所有绘制基于其上。
 
同大多数网游一样, JGnet 中大量使用自定义图形格式 ( 后缀 .img) ,其解释与转化通过 CompressedImage 类得以完成,由于自定义格式通常能更简洁的声明图像中各元素关系,所以 JGnet 处理其自定义格式资源会比加载普通图像资源更快。
 
JGnet 会根据环境的不同会采用 MsJvmGraphics SunJvmGraphics 两套绘图组件(皆继承自 ShortBufferGraphics ),据此我们可以初步断定其运行环境上兼容微软 jvm ,也就是最低可运行于 JRE1.1 之上。
 
讲解结束,下面我以 JGnet 进行针对 DataBuffer 进行渲染的 FPS 测试(对其某些细节进行了使用习惯上的扩展)。
 
1、DataBufferGameFrame.java

package test1;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
 * @author chenpeng
 * @email: [email]ceponline@yahoo.com.cn[/email]
 */
public class DataBufferGameFrame extends Frame implements Runnable {
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private DataBufferCanvas canvas = null;
 private int width = 480;
 
 private int height = 360;
 
 public DataBufferGameFrame() {
  super("JGnet中ShortBufferGraphics使用测试");
  this.setPreferredSize(new Dimension(width + 5, height + 25));
  this.requestFocus();
  this.addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent e) {
    System.exit(0);
   }
  });
  canvas = new DataBufferCanvas();
  this.add(canvas);
  this.pack();
  this.setResizable(false);
  this.setLocationRelativeTo(null);
  this.setIgnoreRepaint(true);
  Thread thread = new Thread(this);
  thread.start();
 }
 public void run() {
  for (;;) {
   canvas.drawScreen();
  }
 }
 public static void main(String[] args) {
  DataBufferGameFrame frm = new DataBufferGameFrame();
  frm.setVisible(true);
 }
}

2、DataBufferCanvas.java

package test1;
import impl.gui.SunJvmGraphics;
import java.awt.*;
import java.util.Random;
import daff.gui.ImageFactory;
import daff.gui.ShortBufferFont;
import daff.gui.ShortBufferGraphics;
import daff.gui.ShortBufferImage;

public class DataBufferCanvas extends Canvas {
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 private boolean isFPS = true;
 private ShortBufferGraphics shortBufferGraphics;
 private MyColor color = new MyColor(255, 255, 255);
 private ShortBufferFont font;
 private boolean initFlag;
 private static ImageFactory factory = new ImageFactory(20);
 private int frames = 0;
 private long totalTime = 0;
 private long curTime = System.currentTimeMillis();
 private long lastTime = curTime;
 private Random rand = new Random();
 private int fps;
 private ShortBufferImage hero_idle_0 = factory
   .getImage("image/hero/idle/idle0_0.img");
 private ShortBufferImage backImage = factory.getImage("back.gif");
 static {
  factory.addArchive("pack/hero.zip");
 }
 public DataBufferCanvas() {
 }
 public void update(Graphics g) {
  paint(g);
 }
 public void paint(Graphics g) {
  if (initFlag) {
   shortBufferGraphics.drawImage(backImage, 0, 0);
   for (int i = 0; i < 1000; ++i) {
    int x = rand.nextInt(480);
    int y = rand.nextInt(360);
    shortBufferGraphics.drawImage(hero_idle_0, x - 100, y - 100);
   }
   shortBufferGraphics.drawString(font, color, ("当前FPS:" + fps)
     .intern(), 15, 15);
   shortBufferGraphics.drawTo(g, 0, 0);
   g.dispose();
  }
 }
 private synchronized void initializtion() {
  shortBufferGraphics = (ShortBufferGraphics) new SunJvmGraphics();
  shortBufferGraphics.createGraphics(getWidth(), getHeight(), this);
  font = new ShortBufferFont("Dialog", 0, 12, this);
  initFlag = true;
 }
 public synchronized void drawScreen() {
  loadGraphics().reset();
  repaint();
  if (isFPS) {
   lastTime = curTime;
   curTime = System.currentTimeMillis();
   totalTime += curTime - lastTime;
   if (totalTime > 1000) {
    totalTime -= 1000;
    fps = frames;
    frames = 0;
   }
   ++frames;
  }
  Thread.yield();
 }
 public synchronized ShortBufferGraphics loadGraphics() {
  if (!initFlag) {
   initializtion();
  }
  return shortBufferGraphics;
 }
}
 
场景图1 、角色图 FPS 140 - 160
 
 
场景图1 、角色图100  FPS 115 - 125
 
场景图1 、角色图1000  FPS 55 - 70
 
 
通过简单比较后我们可以发现, Graphics 绘图无论在直接 BufferedImage 双缓冲或者利用 BufferStrategy 以换取硬件加速的前提下,都无法与使用 DataBuffer 直接进行像素绘图的 ShortBufferGraphics 相比, BufferStrategy 在绘制千人时更出现了令笔者整个人都斯巴达了的 4 ||| ,即使在测试中笔者改 BufferedImage VolatileImage 也收效甚微,在配置较高的微机上应当会有些许改善,但用户使用机型却并非我们所能控制的。
 
由于 JGnet 中直接获得图像 DataBuffer short[] 形式,并以此进行图像绘制,所以  ShortBufferImage ShortBufferGraphics 称得上是一组先天的 Java 图形缓冲对象,就效率上讲,使用 ShortBufferGraphics 绘图获得的 FPS 明显高于使用 Graphics2D 绘图,这是数组操作先天性的效率优势所决定的。但缺点在于, ShortBufferImage ShortBufferGraphics 其并非直接继承自 Image Graphics ,两者间函数不能完全对应,有些常用方法尚待实现,很多常用方法尚需 ShortBufferGraphics 使用者自行解决。
 
单就效率而言,应该说采取 DataBuffer 进行缓冲绘图明显优于单纯利用 BufferStrategy 或者 BufferedImage 获得 Graphics 后进行绘图。
 
总体上看,如果我们追求最高 FPS ,则使用 BufferedImage 产生 DataBuffer 进行象素处理最为高效的,与方法二混用后效率还将更高,但遗憾的是目前缺少相关开源组件,可用资源上略显不足,有待相关公司或者个人提供,另外我们也可以考虑功能混用,即主体部分使用 DataBuffer 渲染,未实现部分以 Graphics 补足,但这肯定会对运行效率产生影响。
 
由于 JGnet 目前属于闭源项目,所以我无法公开其任何代码实现。为此我自制了一个非常简单的 DataBuffer 中图像处理类 SimpleIntBufferImage ,以供各位看客参考。
 
SimpleIntBufferImage.java  源码如下:
 
package test1; 

import java.awt.image.BufferedImage; 
import java.awt.image.DataBufferInt; 
import java.awt.image.PixelGrabber; 
import java.io.File; 
import java.io.IOException; 

import javax.imageio.ImageIO; 

/** 
* Copyright 2008 - 2009 
*    
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License. You may obtain a copy of 
* the License at 
*    
* [url]http://www.apache.org/licenses/LICENSE-2.0[/url] 
*    
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
* License for the specific language governing permissions and limitations under 
* the License. 
*    
* @project loonframework 
* @author chenpeng 
* @email:[email]ceponline@yahoo.com.cn[/email] 
* @version 0.1 
*/
 
public  class SimpleIntBufferImage { 

   private BufferedImage bufferImage; 

   private  int width; 

   private  int height; 

   final  private  int[] pixels; 

   final  private  int[] clear; 

   public SimpleIntBufferImage( final String fileName) { 
     try { 
      BufferedImage tmpImage = ImageIO.read( new File(fileName)); 
      width = tmpImage.getWidth( null); 
      height = tmpImage.getHeight( null); 
      clear =  new  int[width * height]; 
      bufferImage =  new BufferedImage(width, height, 
          BufferedImage.TYPE_INT_ARGB); 
      pixels = ((DataBufferInt) bufferImage.getRaster().getDataBuffer()) 
          .getData(); 
      PixelGrabber pgr =  new PixelGrabber(tmpImage, 0, 0, width, height, 
          pixels, 0, width); 
       try { 
        pgr.grabPixels(); 
      }  catch (InterruptedException ex) { 
      } 
    }  catch (IOException ex) { 
       throw  new RuntimeException(ex); 
    } 
  } 

   /** 
    * 清空pixels数据 
    *    
    */
 
   public  void clearImage() { 
    System.arraycopy(clear, 0, pixels, 0, clear.length); 
  } 

   /** 
    * 删除指定颜色 
    *    
    * @param rgbs 
    */
 
   public  void clearRGBs( final  int[] rgbs) { 
     for ( int i = 0; i < pixels.length; i++) { 
       int npixel = (255 << 24) + (rgbs[0] << 16) + (rgbs[1] << 8) 
          + rgbs[2]; 
       if (pixels[i] == npixel) { 
        pixels[i] = 0; 
      } 
    } 
  } 

   /** 
    * 转换当前贴图为灰白位图 
    *    
    */
 
   public  void convertToGray() { 
     for ( int i = 0; i < pixels.length; i++) { 
      pixels[i] = colorToGray(pixels[i]); 
    } 
  } 

   /** 
    * 转换当前贴图为异色(反色)位图 
    *    
    */
 
   public  void convertToXor() { 
     for ( int i = 0; i < pixels.length; i++) { 
      pixels[i] = colorToXor(pixels[i]); 
    } 
  } 

   /** 
    * 获得r,g,b 
    *    
    * @param pixel 
    * @return 
    */
 
   private  int[] getRGBs( final  int pixel) { 
     int[] rgbs =  new  int[3]; 
    rgbs[0] = (pixel >> 16) & 0xff; 
    rgbs[1] = (pixel >> 8) & 0xff; 
    rgbs[2] = (pixel) & 0xff; 
     return rgbs; 
  } 

   /** 
    * 转换指定像素为灰白 
    *    
    * @param pixel 
    * @return 
    */
 
   private  int colorToGray( final  int pixel) { 
     int[] rgbs = getRGBs(pixel); 
     int value = ( int) (0.299 * rgbs[0] + 0.587 * rgbs[1] + 0.114 * rgbs[2]); 
     int npixel = (255 << 24) + (value << 16) + (value << 8) + value; 
     return npixel; 
  } 

   /** 
    * 异或指定像素 
    *    
    * @param pixel 
    * @return 
    */
 
   private  int colorToXor( final  int pixel) { 
     int[] rgbs = getRGBs(pixel); 
     int r = rgbs[0] ^ 0xff; 
     int g = rgbs[1] ^ 0xff; 
     int b = rgbs[2] ^ 0xff; 
     int npixel = (255 << 24) + (r << 16) + (g << 8) + b; 
     return npixel; 
  } 

   /** 
    * copy指定贴图于本身位图之上 
    *    
    * @param simpleImage 
    * @param left 
    * @param top 
    */
 
   public  void copyImage( final SimpleIntBufferImage simpleImage, 
       final  int left,  final  int top) { 
     int[] src = simpleImage.getPixels(); 
     int srcWidth = simpleImage.getWidth(); 
     int srcHeight = simpleImage.getHeight(); 
     for ( int x = 0, x1 = left; x < srcWidth && x < width && x1 < width; x++, x1++) { 
       for ( int y = 0, y1 = top; y < srcHeight && x < height 
          && y1 < height; y++, y1++) { 
         int npixels = src[x + y * srcWidth]; 
         if (npixels != -16777216) { 
          pixels[x1 + y1 * width] = npixels; 
        } 
      } 
    } 
  } 

   /** 
    * 截取指定范围内像素点数组 
    *    
    * @param x 
    * @param y 
    * @param w 
    * @param h 
    * @return 
    */
 
   public  int[] getSubPixels( final  int x,  final  int y,  final  int w,  final  int h) { 
     int[] pixels =  new  int[2 + w * h]; 
    pixels[0] = w; 
    pixels[1] = h; 
     if (pixels ==  null) { 
       return  null
    } 
     for ( int i = 0; i < h; i++) { 
       for ( int j = 0; j < w; j++) { 
        pixels[2 + x + j + (y + i) * w] = getPixel(x + j, y + i); 
      } 
    } 
     return pixels; 
  } 

   /** 
    * 截取指定范围内像素点 
    *    
    * @param x 
    * @param y 
    * @return 
    */
 
   public  int getPixel( final  int x,  final  int y) { 
     return pixels[x + y * width]; 
  } 

   public  int[] getPixels() { 
     return pixels; 
  } 

   public BufferedImage getBufferedImage() { 
     return bufferImage; 
  } 

   public  int getHeight() { 
     return height; 
  } 

   public  void setHeight( int height) { 
     this.height = height; 
  } 

   public  int getWidth() { 
     return width; 
  } 

   public  void setWidth( int width) { 
     this.width = width; 
  } 


 
简单的应用示例:
package test1; 

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.Graphics; 
import java.awt.event.WindowAdapter; 
import java.awt.event.WindowEvent; 

/** 
*    
* Copyright 2008 - 2009 
*    
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License. You may obtain a copy of 
* the License at 
*    
* [url]http://www.apache.org/licenses/LICENSE-2.0[/url] 
*    
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
* License for the specific language governing permissions and limitations under 
* the License. 
*    
* @project loonframework 
* @author chenpeng 
* @email:[email]ceponline@yahoo.com.cn[/email] 
* @version 0.1 
*/
 
public  class Sample  extends Frame  implements Runnable{ 

   /** 
    *    
    */
 
   private  static  final  long serialVersionUID = 1L; 

   private SimpleIntBufferImage simple1 =  new SimpleIntBufferImage( "back.gif"); 

   private SimpleIntBufferImage simple2 =  new SimpleIntBufferImage( 
       "idle0_0.png"); 

   private  int width = 480; 

   private  int height = 360; 

   public Sample() { 
     super( "自制SimpleIntBufferImage示例"); 
     this.setBackground(Color.black); 
    simple1.convertToXor(); 
    simple1.convertToXor(); 
     this.setPreferredSize( new Dimension(width, height)); 
     this.requestFocus(); 
     this.addWindowListener( new WindowAdapter() { 
       public  void windowClosing(WindowEvent e) { 
        System.exit(0); 
      } 
    }); 
     this.pack(); 
     this.setResizable( false); 
     this.setLocationRelativeTo( null); 
     this.setIgnoreRepaint( true); 
    Thread thread =  new Thread( this); 
    thread.start(); 
  } 
    
   public  void run() { 
     for(;;){ 
      repaint(); 
       try { 
        Thread.sleep(20L); 
      }  catch (InterruptedException e) { 
      } 
    } 
  } 
    
   public  void update(Graphics g) { 
    paint(g); 
  } 

   public  void paint(Graphics g) { 
    simple1.copyImage(simple2,15,50); 
    g.drawImage(simple1.getBufferedImage(), 0, 0,  null); 
  } 

   public  static  void main(String[] args) { 
    Sample frm =  new Sample(); 
    frm.setVisible( true); 
  } 


 
效果图如下:
 


本文转自 cping 51CTO博客,原文链接:http://blog.51cto.com/cping1982/145872
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值