天天看點

J2me中任意角度圖檔旋轉

  J2me中任意角度圖檔旋轉

作者:pandonix

日期:2007年9月20日

版權聲明:可以任意轉載,轉載時請務必以超連結形式标明文章原始出處和作者資訊及本聲明

原文位址:

http://pandonix.javaeye.com/

本文主要讨論在J2me中如何實作任意角度的圖檔旋轉。其實,早在幾年前,minisoyou的BB就已經給出了實作算法,相信做j2me遊戲開發的朋友們也都收藏過該算法。本文從圖像旋轉的基本理論出發,詳細讨論如何使用實作和優化該算法,希望對旋轉算法感興趣的朋友有幫助。

 基本旋轉算法:

讨論位圖旋轉算法,首先得說說最基本的旋轉算法,即:點的旋轉。相信學過計算機圖形學的朋友們,對該算法不會陌生。

假設,P(x,y)旋轉t角度後,得到P’(x’,y’),P與P’之間的關系如下:

(x’,y’) = (x cos(t) + y sin(t),y cos(t) - x sin(t))

 位圖旋轉:

根據以上算法,可以直覺的想到位圖旋轉算法,即周遊位圖中的所有像素點,對每個像素點進行旋轉變換。

但是在j2me中真正實作位圖的旋轉,還需要解決以下幾點問題:

1、如何擷取圖檔的像素數組,且得到的像素值是包含了alpha高位的,換句話說,要支援透明;

2、由于是任意角度旋轉,旋轉後的圖像尺寸如何計算;

3、旋轉算法可以優化,提高計算速度;

4、旋轉得到的像素數組,如何繪制到canvas上;

5、旋轉後能否直接得到一個Image對象;

6、旋轉算法都是基于小數運算的,在CLDC1.0的裝置上,如何實作旋轉;

7、旋轉後的圖像是否存在失真;

 J2me中的像素操作熟悉j2me的朋友對于問題一的回答應該是肯定的。我們有兩種方法擷取像素數組,一種是使用midp2.0中的getRGB方法,另一種是使用NokiaUI中的getPixels方法,兩種方法各有優缺點。其中,getRGB方法并非所有midp2.0都手機都支援,本人在Nokia6600上面使用該方法就出現問題,除此之外,getRGB隻能擷取到8888格式的ARGB像素值,即通常說的256色。而NokiaUI重載了三個getPixels方法,不僅支援8888格式,還支援4444格式,即,可以使用short數組來存儲像素值,而無須使用int數組。

由于getRGB方法很簡單,參考API文檔就能使用,是以,在此隻列出NokiaUI中如何擷取像素數組:

java 代碼

  1. public short[] pixelsProduce(Image src)   
  2. {   
  3.     int w = src.getWidth();   
  4.     int h = src.getHeight();   
  5.     short[] _pixels = new short[w * h];   
  6.     //建立可變圖像   
  7.     Image img = DirectUtils.createImage(w, h, 0);   
  8.     Graphics g = img.getGraphics();   
  9.     DirectGraphics dg = DirectUtils.getDirectGraphics(g);   
  10.     //先将原圖繪制到建立好的可變圖像上   
  11.     dg.drawImage(src,0,0,0,0);   
  12.     //再擷取該可變圖像的像素數組   
  13.     dg.getPixels(_pixels,0, w, 0, 0, w, h,4444);   
  14.     return _pixels;   
  15. }   

解決了擷取圖檔像素數組的問題,繪制旋轉後的像素數組的問題也迎刃而解了。Midp2.0中,可以使用drawRGB,而NokiaUI中,則使用drawPixels。當然,如果考慮到繪制的效率,可以将像素數組轉化為Image對象,這樣雖然建立時耗費了時間,但是在繪制時,卻比直接繪制像素數組更加有效率。為此,Midp2.0中,可使用Image.createRGBImage方法,而NokiaUI中,則可以采用以下方法來擷取Image對象:

java 代碼

  1. public Image createImg(short[] pixels,int _width,int _height)   
  2. {   
  3.     //建立可變圖像   
  4.     Image img = DirectUtils.createImage(_width, _height, 0);   
  5.     Graphics g = img.getGraphics();   
  6.     DirectGraphics dg = DirectUtils.getDirectGraphics(g);   
  7.     //将像素數組繪制到建立好的可變圖像上   
  8.     dg.drawPixels(pixels, true, 0, _width, 0, 0, _width, _height, 0,4444);   
  9.     return img;   
  10. }   

算法實作排除了像素操作的障礙以後,就可以來實作旋轉算法了。首先,考慮使用小數來實作該算法。接下來需要考慮的問題是,旋轉後的圖檔的尺寸該設定為多大,因為其直接影響到旋轉後的像素數組的大小。不考慮存儲空間的情況下,可以如此定義尺寸:原圖檔矩形的外接圓的外切矩形,就是旋轉後圖檔所在的矩形,如圖所示。雖然,這樣定義比較浪費空間,但是,原圖檔沿任意角度選擇後的像素數組都可以被覆寫到。定義好尺寸之後,最基本的旋轉算法就容易了,算法步驟如下:1、計算旋轉後圖檔尺寸,并定義好旋轉後像素數組newPixels[];2、将newPixels的各數組元素初始化為透明,即:0x0;3、按從左到右,從上到下的順序,周遊原圖檔數組,計算出每個像素點旋轉後的坐标,并将其複制到newPixels中

算法代碼如下:

java 代碼

  1. public static int[] rotate2(int[] _pixels,int _width,int _height,double _angle)   
  2. {   
  3.     int i,j;   
  4.     double radius = Math.sqrt(_width*_width + _height*_height);   
  5.     int r = (int)radius;   
  6.     int[] newPixels = new int[r*r];   
  7.     for(i = 0; i < newPixels.length;i++)   
  8.     {   
  9.             newPixels[i] = (TRANSPARENT)<<24;   
  10.     }   
  11.     double x1,y1;   
  12.     int x2,y2;   
  13.     double cos = Math.cos(_angle);   
  14.     double sin = Math.sin(_angle);   
  15.     for(i = 0; i < _height;i++)   
  16.     {   
  17.         for(j = 0;j < _width;j++)   
  18.         {   
  19.             x1 = j + (- _width)/2;   
  20.             y1 = i + (- _height)/2;   
  21.             x2 = (int)(x1*cos - y1*sin);   
  22.             y2 = (int)(x1*sin + y1*cos);   
  23.             x2 += r/2;   
  24.             y2 += r/2;   
  25.             newPixels[y2*r+x2] = _pixels[i*_width+j];   
  26.         }   
  27.     }   
  28.     return newPixels;   
  29. }   

實作後的效果如圖所示,基本上實作了旋轉。但遺憾的是,旋轉後的圖像明顯存在“壞點”,在圖像中,有很多像素點沒有被映射到,是以沒有像素值,造成了該點仍然是透明的。而且,圖檔的失真程度不能接受。

解決“壞點”仔細思考一下,壞點出現的主要原因在于,旋轉公式的定義域是整個實數域,而像素點顯然是離散點,是以,在進行映射時,會産生一定的誤差,是以造成本該被定義的像素點,沒有被映射到。其實,在圖像處理領域,已經存在很多優化的算法,而且理論都基于數學的。

回到我們的問題,為了解決壞點,我們可以這樣來考慮:改周遊原圖像數組為周遊newPixels數組,這樣就能保證所有的newPixels可以被覆寫。具體代碼如下:

java 代碼

  1. public static int[] rotate(int[] _pixels,int _width,int _height,double _angle)   
  2. {   
  3.     int i,j;   
  4.     double radius = Math.sqrt(_width*_width + _height*_height);   
  5.     int r = (int)radius;   
  6.     int[] newPixels = new int[r*r];   
  7.     for(i = 0; i < newPixels.length;i++)   
  8.     {   
  9.             newPixels[i] = (TRANSPARENT)<<24;   
  10.     }   
  11.     double x1,y1;   
  12.     int x2,y2;   
  13.     double cos = Math.cos(_angle);   
  14.     double sin = Math.sin(_angle);   
  15.     for(i = 0;i < r;i++)   
  16.     {   
  17.         for(j = 0;j < r;j++)   
  18.         {   
  19.             x1 = j + (- r)/2;   
  20.             y1 = i + (- r)/2;   
  21.             x2 = (int)(x1*cos + y1*sin);   
  22.             y2 = (int)(-x1*sin + y1*cos);   
  23.             if(x2 >= -_width/2&&x2< _width/2&&y2 >= -_height/2&&y2< _height/2)   
  24.             {   
  25.                 int k = (y2 + _height/2)*_width+x2+_width/2;   
  26.                 newPixels[i*r + j] = _pixels[k];   
  27.             }   
  28.         }   
  29.     }   
  30.     return newPixels;   
  31. }   

  效果如圖所示,基本上解決圖像中的壞點,失真度基本可以接受。如果你覺得不符合要求,沒關系,下面會介紹BB的算法。速度優化

上面這種算法的時間複雜程度是O(n^2),那麼對該算法是否可以優化呢?參考了《A Fast Algorithm for Rotating Bitmaps》一文,可以對算法作以下優化:

  1. public static int[] rotate3(int[] _pixels,int _width,int _height,double _angle)   
  2. {   
  3.     int i,j;   
  4.     double radius = Math.sqrt(_width*_width + _height*_height);   
  5.     int r = (int)radius;   
  6.     int[] newPixels = new int[r*r];   
  7.     for(i = 0; i < newPixels.length;i++)   
  8.     {   
  9.             newPixels[i] = (TRANSPARENT)<<24;   
  10.     }   
  11.     double x2,y2;   
  12.     int x3,y3;   
  13.     double cos = Math.cos(_angle);   
  14.     double sin = Math.sin(_angle);   
  15.     for(i = 0;i < r;i++)   
  16.     {   
  17.         x2 = (-r/2)*cos + (i - r/2)*sin;   
  18.         y2 = (r/2)*sin + (i - r/2)*cos;   
  19.         x3 = (int)x2;   
  20.         y3 = (int)y2;   
  21.         for(j = 0;j < r;j++)   
  22.         {   
  23.             if(x3 >= -_width/2&&x3< _width/2&&y3 >= -_height/2&&y3< _height/2)   
  24.             {   
  25.                 newPixels[i*r +j] = _pixels[(y3 + _height/2)*_width+x3+_width/2];   
  26.             }   
  27.             x2 += cos;   
  28.             y2 -= sin;   
  29.             x3 = (int)x2;   
  30.             y3 = (int)y2;   
  31.         }   
  32.     }   
  33.     return newPixels;   
  34. }   

使用定點數實作其實,主要就是實作Sin和Cos的求值,基本思路是:将Sin和Cos值放大255倍,進行計算以後,對結果再進行縮小處理,具體請參考代碼Rotation2.javaBB的算法應該說,BB給出的算法的失真程度最小,他是對目标像素點進行了一個權重計算,但是,遺憾的是,翻閱了很多資料,我也沒有找到該算法的相關理論介紹。若有朋友了解,請正解。具體代碼請參考Rotation_4444.java總結與思考

總的說來,旋轉後的位圖都存在不同情況的失真,是以在實際開發過程中,需要視情況而定。除此之外,不同的終端對于透明的不同,例如,K700在透明方面問題就比較多。