前段時間接觸了一些數字圖像處理的問題,在1位師兄的指導下,在j2me平台,完成了一些基本的2D圖像處理算法。就當是對這段知識做一下總結,決定把這些算法寫出來,和各位朋友共同探討。這篇文章先介紹圖像放大縮小的實現,程序是以Nokia S40的機器為平台實現的。
1、實現圖形縮放的基本思想:
圖像的變形變換,簡單的說就是把源圖像每個點坐標通過變形運算轉為目標圖像相應點的新坐標,但是這樣會導致一個問題就是目標點的坐標通常不會是整數。所以我們在做放大變換時,需要計算生成沒有被映射到的點;而在縮小變換時,需要刪除一些點。這裡我們采用最簡單的一種插值算法:“最近鄰域法”。顧名思義,就是把非整數坐標作一個四捨五入,取最近的整數點。
看下面的一個圖片放大的例子,左圖為原始圖像,右圖為放大1倍的圖像。裡面的數字,表示所在像素的信息
1 2 1 1 2 2
5 4 5 5 4 4
2、對於圖片像素的操作:
獲取Image圖片像素信息:
標准的midp1.0沒有提供獲取圖片像素信息的函數,對於NOKIA的機器,我們可以采用Nokia SDK提供的API獲取像素信息。具體程序如下:
g = image.getGraphics()
DirectGraphics dg = DirectUtils.getDirectGraphics(g);
dg.getPixels(short[] pixels, int offset, int scanlength, int x,int y, int width, int height, int format)
參數介紹:
short[] pixels: 用於接收像素信息的數組
int offset:這篇文章中的用到的地方,添0就可以了
int scanlength:添圖片的寬度就行了
int x:添0
int y:添0
int width:圖片寬度
int height:圖片高度
int format:444,表示圖形格式,好像Nokia S40的機器都是采用444格式表示RGB顏色的。就是紅,綠,藍各用4位表示,至於可以表示透明色ARGB的4444格式,應該是機器硬件實現的。
想具體了解Nokia SDK的信息,可以查看Nokia SDK的幫助文檔。
使用像素信息數組生成Image圖片:
image = Image.createImage(w, h);
g = image.getGraphics()
DirectGraphics dg = DirectUtils.getDirectGraphics(g);
dg.drawPixels(short[] pixels,boolean transparency, int offset, int scanlength, int x, int y, int width,int height, int manipulation, int format)
short[] pixels:像素信息數組
boolean transparency:是否包含alpha位信息
int offset:添 0
int scanlength:添圖片的寬度就行了
int x:添 0
int y:添 0
int width:圖片寬度
int height:圖片高度
int manipulation:添 0
int format:444
下面開始介紹具體的算法,首先給出圖像縮放的完整函數,然後對代碼,分段進行解釋
/*********************************
* @todo 圖片放大縮小
* @param srcImg 原始圖片
* @param desW 變化後圖片的寬
* @param desH 變化後圖片的高
* @return 處理後的圖片
*********************************/
private Image ZoomImage(Image srcImg, int desW, int desH) {
int srcW = srcImg.getWidth(); //原始圖像寬
int srcH = srcImg.getHeight(); //原始圖像高
short[] srcBuf = new short[srcW * srcH]; //原始圖片像素信息緩存
//srcBuf獲取圖片像素信息
Image desImg = Image.createImage(srcW, srcH);
if (srcImg.isMutable()) { /*如果是可變圖像*/
DirectUtils.getDirectGraphics(srcImg.getGraphics()).
getPixels(srcBuf, 0, srcW, 0, 0, srcW, srcH, 444);
} else { /*如果是非可變圖像*/
desImg.getGraphics().drawImage(srcImg, 0, 0, 0);
DirectUtils.getDirectGraphics(desImg.getGraphics()).
getPixels(srcBuf, 0, srcW, 0, 0, srcW, srcH, 444);
}
//計算插值表
short[] tabY = new short[desH];
short[] tabX = new short[desW];
int sb = 0;
int db = 0;
int tems = 0;
int temd = 0;
int distance = srcH > desH ? srcH : desH;
for (int i = 0; i <= distance; i++) { /*垂直方向*/
tabY[db] = (short) sb;
tems += srcH;
temd += desH;
if (tems > distance) {
tems -= distance;
sb++;
}
if (temd > distance) {
temd -= distance;
db++;
}
}
sb = 0;
db = 0;
tems = 0;
temd = 0;
distance = srcW > desW ? srcW : desW;
for (int i = 0; i <= distance; i++) { /*水平方向*/
tabX[db] = (short) sb;
tems += srcW;
temd += desW;
if (tems > distance) {
tems -= distance;
sb++;
}
if (temd > distance) {
temd -= distance;
db++;
}
}
//生成放大縮小後圖形像素buf
short[] desBuf = new short[desW * desH];
int dx = 0;
int dy = 0;
int sx = 0;
int sy = 0;
int oldy = -1;
for (int i = 0; i < desH; i++) {
if (oldy == tabY[i]) {
System.arraycopy(desBuf, dy - desW, desBuf, dy, desW);
} else {
dx = 0;
for (int j = 0; j < desW; j++) {
desBuf[dy + dx] = srcBuf[sy + tabX[j]];
dx++;
}
sy += (tabY[i] - oldy) * srcW;
}
oldy = tabY[i];
dy += desW;
}
//生成圖片
desImg = Image.createImage(desW, desH);
DirectUtils.getDirectGraphics(desImg.getGraphics()).
drawPixels(desBuf, true, 0, desW, 0, 0, desW, desH, 0, 444);
return desImg;
}
首先看函數的頭兩句,很容易,就是獲取原始圖片的寬度和高度
int srcW = srcImg.getWidth(); //原始圖像寬
int srcH = srcImg.getHeight(); //原始圖像高
接下來一句我們要定義一個short型數組,作為獲取原始圖片像素信息的緩存
short[] srcBuf = new short[srcW * srcH];
再下來一段,有的朋友可能會有些不明白,這裡要解釋一下。由於getPixels()這個函數,只能獲取可變圖像的像素信息,非可變圖像,無法獲取像素信息。所以我們要用srcImg.isMutable() 來判斷,原始圖像是不是可變圖像,然後分兩種情況來處理。如果srcImg是可變圖像,我們就直接用getPixels()來獲取它的像素信息,並保存在srcBuf裡。如果srcImg不是可變圖像,我們就需要把srcImage畫到事先生成的可變圖像desImg上,然後再獲取desImg的像素信息。
Image desImg = Image.createImage(srcW, srcH);
if (srcImg.isMutable()) { /*如果是可變圖像*/
DirectUtils.getDirectGraphics(srcImg.getGraphics()).
getPixels(srcBuf, 0, srcW, 0, 0, srcW, srcH, 444);
} else { /*如果是非可變圖像*/
desImg.getGraphics().drawImage(srcImg, 0, 0, 0);
DirectUtils.getDirectGraphics(desImg.getGraphics()).
getPixels(srcBuf, 0, srcW, 0, 0, srcW, srcH, 444);
}
再往下就是縮放算法的重點:插值表的生成。插值表分水平差值表和垂直插值表,我們要分別生成原始圖像矩陣的2種插值表,然後利用插值表生成放大縮小後的圖像矩陣。由於這個內容比較抽象,很難用文字表述清楚,所以我們用實例進行介紹。
大家看下面這個水平的1*4的表格
-----------------
| 0 | 1 | 2 | 3 |
-----------------
如果要將這個表格放大成1*6的表格,放大的表格比原始表格多出了2個格子,我們只能對這多出來的2個格子進行插值,才能完成放大的操作。現在結合生成水平插值表的代碼來完成這個過程。
distance = srcW > desW ? srcW : desW;
for (int i = 0; i <= distance; i++) { /*水平方向*/
tabX[db] = (short) sb;
tems += srcW;
temd += desW;
if (tems > distance) {
tems -= distance;
sb++;
}
if (temd > distance) {
temd -= distance;
db++;
}
}
很明顯原始表格寬度srcW = 4;放大後的表格寬度desW = 6;所以distance = desW = 6
接下來進入for循環,我們一步步的演算其循環的過程
-----------------------------------------------
| i| tabX賦值操作| tems | temd| sb | db |
-----------------------------------------------
|0| tabX[0] = 0 | 4 | 6 | 0 | 0 |
-----------------------------------------------
|1| tabX[0] = 0 | 2 | 6 | 1 | 1 |
-----------------------------------------------
|2| tabX[0] = 1 | 6 | 6 | 1 | 2 |
-----------------------------------------------
|3| tabX[0] = 1 | 4 | 6 | 2 | 3 |
-----------------------------------------------
|4| tabX[0] = 2 | 2 | 6 | 3 | 4 |
-----------------------------------------------
|5| tabX[0] = 3 | 6 | 6 | 3 | 5 |
-----------------------------------------------
|6| tabX[0] = 3 | 4 | 6 | 4 | 6 |
-----------------------------------------------
有此得到放大後1*6的表格為下圖所示。其中每一個單元格中的數字n表示這個單元格的內容,和原始表格中第n個單元格的內容一樣。
--------------------------
| 0 | 1 | 1 | 2 | 3 | 3 |
--------------------------
例如,左圖為原始表格,右圖為放大的表格
--------------------- --------------------------------
| 紅 | 綠 | 蘭 | 紫 | | 紅 | 綠 | 綠 | 蘭 | 紫 | 紫 |
--------------------- --------------------------------
同樣,垂直方向的插值表我們也可以用相同的方法獲得。
有了2個插值表,下面就可以生成放大和縮小後的圖像了。
short[] desBuf = new short[desW * desH];
int dx = 0;
int dy = 0;
int sx = 0;
int sy = 0;
int oldy = -1;
for (int i = 0; i < desH; i++) {
if (oldy == tabY[i]) { /**********情況一**********/
System.arraycopy(desBuf, dy - desW, desBuf, dy, desW);
} else { /**********情況二**********/
dx = 0;
for (int j = 0; j < desW; j++) {
desBuf[dy + dx] = srcBuf[sy + tabX[j]];
dx++;
}
sy += (tabY[i] - oldy) * srcW;
}
oldy = tabY[i];
dy += desW;
}
desBuf是用來保存放大縮小後的圖像數據。例如我們把一個4*4像素的圖像A放大成6*6的圖像B,據前面的介紹我們可以生成2個插值表。tabX = {0,1,1,2,3,3},tabY = {0,1,1,2,3,3}。
在循環中會判斷是否oldy 等於 tabY[i],這個操作等同於tabY[i-1]是否等於tabY[i]。如果等於,表示圖像B前一行已經生成的數據和即將要生成的第i行數據相同,則只要執行System.arraycopy(desBuf, dy - desW, desBuf, dy, desW)把上一行的數據復制過來即可;如果不等,則需要對照水平插值表tabX生成這一行的數據。
算法演示過程如下:
----------------------------
| i|oldy|tabY[i]|運算情況|
----------------------------
|0|-1 | 0 | 情況2 |
----------------------------
|1| 0 | 1 | 情況2 |
----------------------------
|2| 1 | 1 | 情況1 |
----------------------------
|3| 1 | 2 | 情況2 |
----------------------------
|4| 2 | 3 | 情況2 |
----------------------------
|5| 3 | 3 | 情況1 |
----------------------------
然後我們用desBuf生成最終放大或縮小後的圖片
desImg = Image.createImage(desW, desH);
DirectUtils.getDirectGraphics(desImg.getGraphics()).
drawPixels(desBuf, true, 0, desW, 0, 0, desW, desH, 0, 444);
return desImg;
最後要說明一點的是,由於該算法中使用了Image.createImage(w, h)來創建圖像,這個函數會創建一個w*h像素的全白可變圖像,所以透明圖片放大縮小後,背景不再透明,而是白色了,這是j2me本身的缺憾和算法沒有關系。對於這個問題,有一個解決辦法,就是程序的圖片不采用Image來保存,而是采用short[]數組保存,畫圖的時候用drawPixels()來畫圖。^-^