1. 程式人生 > 實用技巧 >Java遊戲開發中怎樣才能獲得更快的FPS?

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,還要老生常談的從ImageGraphics及其相關子類入手。 那麼,對這些盡人皆知的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、完全在BufferedImageDataBuffer中進行影象處理 每個BufferedImage都有一個與之對應得WritableRaster物件(getRaster方法獲得),通過它我們獲得指定BufferedImageDataBuffergetDataBuffer方法獲得),與方法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][email protected][/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.p_w_picpath.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][email protected][/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.p_w_picpath.BufferStrategy;
import java.awt.p_w_picpath.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、角×××1 FPS 80 - 90
場景圖1、角×××100 FPS 25 - 35 場景圖1、角×××1000 FPS 3 - 6 三、完全在BufferedImageDataBuffer中進行影象處理 實際上在Java網遊海盜時代、Java網遊暗影世界等網路遊戲、還有netbaens以及其它種種不算慢的Java應用中,都存在大量使用DataBuffer進行的影象渲染;但在使用方式上,卻只有暗影使用的JGnet引擎與常用的ImageGraphics搭配模式較為接近,所以此示例使用了JGnet的部分程式碼構建。 PS:實際上漢森的JGnet客戶端引擎目前並未開源,此部分程式碼得自其主頁homepage.jar的反編譯,是前天我看csdnCTO欄目專訪漢森老總後去其首頁發現的;視訊中說JGnet有部分程式碼準備開源,我估計應該是homepage.jar裡這部分,因為沒有混淆且功能較少,其網站地址[url]http://www.handseeing.com[/url],純Applet站點)。 為給不願反編譯的看客提供方便,這裡我稍微對JGnet進行一點分析(homepage.jar中的那部分)。 就程式碼來看,JGnet中所有UI繼承自GUIWidgetGUIWidget繼承自Container),所有JGnet產生的GUI元件也都應當繼承它。JGnet提供了一個GUIRoot為底層面板,所有GUIWidget衍生元件都應附著於GUIRoot之上,另外該引擎提供有AbstractGameClientClientContext介面及相關實現用以排程相關元件,JGnetGUIRoot載體可以使用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的影象繪製主要使用其內部提供的ShortBufferImageShortBufferGraphics以及相關衍生類。ShortBufferImage是一個抽象類,沒有父類,所有JGnet中圖形資源都將表現為ShortBufferImage,繪製ShortBufferImage需要使用daff.gui包提供的專用畫布ShortBufferGraphics,實際上ShortBufferGraphics是一個針對於ShortBufferImage的象素渲染器,內部以short陣列形式儲存有一個空白BufferedImageDataBuffer,所有繪製基於其上。 同大多數網遊一樣,JGnet中大量使用自定義圖形格式(字尾.img),其解釋與轉化通過CompressedImage類得以完成,由於自定義格式通常能更簡潔的宣告影象中各元素關係,所以JGnet處理其自定義格式資源會比載入普通影象資源更快。 JGnet會根據環境的不同會採用MsJvmGraphicsSunJvmGraphics兩套繪圖元件(皆繼承自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][email protected][/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("p_w_picpath/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、角×××1 FPS 140 - 160 場景圖1、角×××100 FPS 115 - 125
場景圖1、角×××1000 FPS 55 - 70 通過簡單比較後我們可以發現,Graphics繪圖無論在直接BufferedImage雙緩衝或者利用BufferStrategy以換取硬體加速的前提下,都無法與使用DataBuffer直接進行畫素繪圖的ShortBufferGraphics相比,BufferStrategy在繪製千人時更出現了令筆者整個人都斯巴達了的4|||,即使在測試中筆者改BufferedImageVolatileImage也收效甚微,在配置較高的微機上應當會有些許改善,但使用者使用機型卻並非我們所能控制的。 由於JGnet中直接獲得影象DataBuffershort[]形式,並以此進行影象繪製,所以 ShortBufferImageShortBufferGraphics稱得上是一組先天的Java圖形緩衝物件,就效率上講,使用ShortBufferGraphics繪圖獲得的FPS明顯高於使用Graphics2D繪圖,這是陣列操作先天性的效率優勢所決定的。但缺點在於,ShortBufferImageShortBufferGraphics其並非直接繼承自ImageGraphics,兩者間函式不能完全對應,有些常用方法尚待實現,很多常用方法尚需ShortBufferGraphics使用者自行解決。 單就效率而言,應該說採取DataBuffer進行緩衝繪圖明顯優於單純利用BufferStrategy或者BufferedImage獲得Graphics後進行繪圖。 總體上看,如果我們追求最高FPS,則使用BufferedImage產生DataBuffer進行象素處理最為高效的,與方法二混用後效率還將更高,但遺憾的是目前缺少相關開源元件,可用資源上略顯不足,有待相關公司或者個人提供,另外我們也可以考慮功能混用,即主體部分使用DataBuffer渲染,未實現部分以Graphics補足,但這肯定會對執行效率產生影響。 由於JGnet目前屬於閉源專案,所以我無法公開其任何程式碼實現。為此我自制了一個非常簡單的DataBuffer中影象處理類SimpleIntBufferImage,以供各位看客參考。 SimpleIntBufferImage.java 原始碼如下: package test1;

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

import javax.p_w_picpathio.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][email protected][/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;
}

}