| 网站首页 | 文章中心 | 电子书下载 | 矢量图库 | 视频教程 | 素材下载 | 程序代码下载 | JS代码 | 论坛 | 
常用软件类:
|杀毒安全 |联络聊天 |网络软件 |多媒体类 |系统工具 |图形图像 |系统工具 |应用软件 |行业软件
开发设计类:
|动画制作 |图像处理 |3D设计 |操作系统 |站长学院 |网络相关 |WEB设计 |数据库类 |程序开发
J2me实现任何角度的图片旋转
   本文主要讨论在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 代码

  public short[] pixelsProduce(Image src)

  {

  int w = src.getWidth();

  int h = src.getHeight();

  short[] _pixels = new short[w * h];

  //创建可变图像

  Image img = DirectUtils.createImage(w, h, 0);

  Graphics g = img.getGraphics();

  DirectGraphics dg = DirectUtils.getDirectGraphics(g);

  //先将原图绘制到创建好的可变图像上

  dg.drawImage(src,0,0,0,0);

  //再获取该可变图像的像素数组

  dg.getPixels(_pixels,0, w, 0, 0, w, h,4444);

  return _pixels;

  }

  解决了获取图片像素数组的问题,绘制旋转后的像素数组的问题也迎刃而解了。Midp2.0中,可以使用drawRGB,而NokiaUI中,则使用drawPixels。当然,如果考虑到绘制的效率,可以将像素数组转化为Image对象,这样虽然创建时耗费了时间,但是在绘制时,却比直接绘制像素数组更加有效率。为此,Midp2.0中,可使用Image.createRGBImage方法,而NokiaUI中,则可以采用以下方法来获取Image对象:

  java 代码

  public Image createImg(short[] pixels,int _width,int _height)

  {

  //创建可变图像

  Image img = DirectUtils.createImage(_width, _height, 0);

  Graphics g = img.getGraphics();

  DirectGraphics dg = DirectUtils.getDirectGraphics(g);

  //将像素数组绘制到创建好的可变图像上

  dg.drawPixels(pixels, true, 0, _width, 0, 0, _width, _height, 0,4444);

  return img;

  }

 

 算法实现

  排除了像素操作的障碍以后,就可以来实现旋转算法了。首先,考虑使用小数来实现该算法。接下来需要考虑的问题是,旋转后的图片的尺寸该设定为多大,因为其直接影响到旋转后的像素数组的大小。

  不考虑存储空间的情况下,可以如此定义尺寸:原图片矩形的外接圆的外切矩形,就是旋转后图片所在的矩形,如图所示。虽然,这样定义比较浪费空间,但是,原图片沿任意角度选择后的像素数组都可以被覆盖到。

  定义好尺寸之后,最基本的旋转算法就容易了,算法步骤如下:

  1、计算旋转后图片尺寸,并定义好旋转后像素数组newPixels[];

  2、将newPixels的各数组元素初始化为透明,即:0x0;

  3、按从左到右,从上到下的顺序,遍历原图片数组,计算出每个像素点旋转后的坐标,并将其复制到newPixels中

  算法代码如下:

  java 代码

  public static int[] rotate2(int[] _pixels,int _width,int _height,double _angle)

  {

  int i,j;

  double radius = Math.sqrt(_width*_width + _height*_height);

  int r = (int)radius;

  int[] newPixels = new int[r*r];

  for(i = 0; i < newPixels.length; i++)

  {

  newPixels[i] = (TRANSPARENT)<<24;

  }

  double x1,y1;

  int x2,y2;

  double cos = Math.cos(_angle);

  double sin = Math.sin(_angle);

  for(i = 0; i < _height; i++)

  {

  for(j = 0; j < _width; j++)

  {

  x1 = j + (- _width)/2;

  y1 = i + (- _height)/2;

  x2 = (int)(x1*cos - y1*sin);

  y2 = (int)(x1*sin + y1*cos);

  x2 += r/2;

  y2 += r/2;

  newPixels[y2*r+x2] = _pixels[i*_width+j];

  }

  }

  return newPixels;

  }

  实现后的效果如图所示,基本上实现了旋转。但遗憾的是,旋转后的图像明显存在“坏点”,在图像中,有很多像素点没有被映射到,所以没有像素值,造成了该点仍然是透明的。而且,图片的失真程度不能接受。

解决“坏点”

  仔细思考一下,坏点出现的主要原因在于,旋转公式的定义域是整个实数域,而像素点显然是离散点,因此,在进行映射时,会产生一定的误差,所以造成本该被定义的像素点,没有被映射到。其实,在图像处理领域,已经存在很多优化的算法,而且理论都基于数学的。

  回到我们的问题,为了解决坏点,我们可以这样来考虑:改遍历原图像数组为遍历newPixels数组,这样就能保证所有的newPixels可以被覆盖。具体代码如下:

  java 代码

  public static int[] rotate(int[] _pixels,int _width,int _height,double _angle)

  {

  int i,j;

  double radius = Math.sqrt(_width*_width + _height*_height);

  int r = (int)radius;

  int[] newPixels = new int[r*r];

  for(i = 0; i < newPixels.length; i++)

  {

  newPixels[i] = (TRANSPARENT)<<24;

  }

  double x1,y1;

  int x2,y2;

  double cos = Math.cos(_angle);

  double sin = Math.sin(_angle);

  for(i = 0; i < r; i++)

  {

  for(j = 0; j < r; j++)

  {

  x1 = j + (- r)/2;

  y1 = i + (- r)/2;

  x2 = (int)(x1*cos + y1*sin);

  y2 = (int)(-x1*sin + y1*cos);

  if(x2 >= -_width/2&&x2< _width/2&&y2 >= -_height/2&&y2< _height/2)

  {

  int k = (y2 + _height/2)*_width+x2+_width/2;

  newPixels[i*r + j] = _pixels[k];

  }

  }

  }

  return newPixels;

  }

  效果如图所示,基本上解决图像中的坏点,失真度基本可以接受。如果你觉得不符合要求,没关系,下面会介绍BB的算法。

  速度优化

  上面这种算法的时间复杂程度是O(n^2),那么对该算法是否可以优化呢?参考了《A Fast Algorithm for Rotating Bitmaps》一文,可以对算法作以下优化:

  public static int[] rotate3(int[] _pixels,int _width,int _height,double _angle)

  {

  int i,j;

  double radius = Math.sqrt(_width*_width + _height*_height);

  int r = (int)radius;

  int[] newPixels = new int[r*r];

  for(i = 0; i < newPixels.length; i++)

  {

  newPixels[i] = (TRANSPARENT)<<24;

  }

  double x2,y2;

  int x3,y3;

  double cos = Math.cos(_angle);

  double sin = Math.sin(_angle);

  for(i = 0; i < r; i++)

  {

  x2 = (-r/2)*cos + (i - r/2)*sin;

  y2 = (r/2)*sin + (i - r/2)*cos;

  x3 = (int)x2;

  y3 = (int)y2;

  for(j = 0; j < r; j++)

  {

  if(x3 >= -_width/2&&x3< _width/2&&y3 >= -_height/2&&y3< _height/2)

  {

  newPixels[i*r +j] = _pixels[(y3 + _height/2)*_width+x3+_width/2];

  }

  x2 += cos;

  y2 -= sin;

  x3 = (int)x2;

  y3 = (int)y2;

  }

  }

  return newPixels;

  }

  使用定点数实现

  其实,主要就是实现Sin和Cos的求值,基本思路是:将Sin和Cos值放大255倍,进行计算以后,对结果再进行缩小处理,具体请参考代码Rotation2.java

  BB的算法

  应该说,BB给出的算法的失真程度最小,他是对目标像素点进行了一个加权计算,但是,遗憾的是,翻阅了很多资料,我也没有找到该算法的相关理论介绍。若有朋友了解,请正解。具体代码请参考Rotation_4444.java

  总结与思考

  总的说来,旋转后的位图都存在不同情况的失真,所以在实际开发过程中,需要视情况而定。除此之外,不同的终端对于透明的不同,例如,K700在透明方面问题就比较多。


  • 上一篇文章:

  • 下一篇文章: 没有了
  • 相关文章