3.3 绘制图像
Image类用来操作图形图像数据,这些数据可以通过调用低级用户界面的软键绘制在屏幕上,也可以与高级用户界面的某些屏幕类或表单组件关联起来,例如在Alert或者List中使用图形。
3.3.1 生成图像数据
应用程序无法使用new方法生成一个Image类,而是使用Image类的静态函数createImage()方法。Image类提供了7个不同的方法来生成图像数据,依据生成方式的不同,图像可以分为以下两类。
n 不变图像(Immutable-Image):从资源包、文件或者网络上加载图像数据生成,一旦生成,即不能被改变。
n 可变图像(Mutable-Image):可变图像在屏幕外内存中创建,并且可以被修改。
|
|
对于在高级用户界面例如Alert、Choice、Form或者ImageItem对象中放置的Image对象,必须是不变图像,因为这些图像被用来在不通知应用程序的情况下更新屏幕内容,只有不变图像才能确保每次更新都是一致的。 |
以下5个方法可以用来创建常规的不变图像。
n createImage(String name):从一个命名资源的图像数据中解码生成一个不变图像,这个命名资源常常是一个图片文件。
n createImage(byte[]imageData,int imageOffset,int imageLength):从存储在字节数组中的数据中生成一个不变图像。
n createImage(InputStream stream):从数据流中解码生成一个不变图像。
n createImage(Image source):从源图像中生成一个新的不变图像。
n createImage(Image image,int x,int y,int width,int height,int transform):从源图像中选取一个区域进行翻转,生成一个新的不变图像。
第一个方法从命名资源中获取图像数据生成一个不变图像,参数name是资源的名称,它所表示的图像数据必须是MIDP实现所支持的图像格式,例如PNG文件格式,有的手机也会支持JPG或者GIF格式,但为了保持较好的移植性,仍建议采用PNG文件。
如果name的值为null,则抛出java.lang.NullPointerException异常。在使用这个方法从命名资源读取数据时,还有可能抛出java.io.IOException异常,而程序中必须对这个异常进行处理,在下列情况下应用程序会抛出I/O异常。
n 所请求的资源不存在。
n 所请求的数据无法加载。
n 所请求的图像数据无法解码。
下面的代码从一个图像文件中读取数据,生成不变图像。
private Image image;
try
{
image = Image.createImage("/tree.png"); //加载文件资源
}catch(java.io.IOException e)
{
System.out.println(e.getMessage()); //捕获I/O异常
}
|
|
在WTK中,图片文件默认放在工程的res目录下,而且文件名不一定以png为后缀,只要它是能自我识别,且被MIDP实现支持的图像格式文件就可以。 |
生成图像对象后,可以调用它的isMutable()方法判断图像是否为可变图像,如果是可变的,则返回true,否则返回false。
boolean mutable = image.isMutable();
由于这里笔者创建的是不变图像,因此返回的mutable值都为false。
第二个方法是从一个字节数组中生成一个不变图像对象,它的3个参数共同指定一个数据源,即字节数组imageDate中由偏移量imageOffset和长度imageLength所指定的一个字节数组,然后createImage方法从这个数据源的数据中解码得到新的不变图像对象。
参数imageOffset和imageLength指定了字节数组imageDate的一个范围,imageOffset参数指定相对于第一个数据字节的偏移量,它的范围必须在[0,(imageDate,length-1)]之间,也就是在imageDate数组的长度之内,imageLength参数指定了需要的字节数量,它必须是一个整数,并且保证imageOffset+imageLength不超过imageDate的长度。
public static Image imgPlayer = Image.createImage(
new byte[] { (byte)0x89, (byte)0x50, (byte)0x4E, (byte)0x47, (byte)0x0D, (byte)0x0A, (byte)0x1A, (byte)0x0A, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0D, (byte)0x49, (byte)0x48, (byte)0x44,
… //中间的数据忽略,请参看光盘
(byte)0x00, (byte)0x00, (byte)0x49, (byte)0x45, (byte)0x4E, (byte)0x44, (byte)0xAE, (byte)0x42, (byte)0x60, (byte)0x82
},
(int)0,
(int)162
);
这个方法从一个数组中加载图像数据,比如从记录存储或者从网络上。将上面创建的两个Image图像绘制在屏幕上,如图3-8所示。
第三种方法是MIDP 2.0新增方法,该方法通过从输入流中解码读取图像数据,从而创建新的Image对象,输入流的获取一般是通过getResourceAsStream返回可以获取的数据流,获取对象包括本地文件系统、远程文件系统、JAR包等资源。

图3-8 分别用图片文件和数组创建不变图像
例如笔者将通过输入流的方式读取前面的PNG文件。
try
{
InputStream stream = getClass().getResourceAsStream("/tree.png");
//用输入流读取文件资源
if(stream != null)
{
image = Image.createImage(stream); //用读取到的数据创建图片
}
}catch(java.io.IOException e)
{
System.out.println(e.getMessage()); //捕获I/O异常
}
第四个方法是从源图像对象中生成一个不变图像,用参数source表示源图像,如果源图像是可变的,那么这个方法将生成一个它的不变图像,内容完全一样。例如在Alert、Choice、ImageItem中只能使用不变图像,用这个方法能够将可变图像应用在这些高级API生成的屏幕中。
第五个方法是MIDP 2.0新增方法,该方法实现了从源图像对象中截取一个区域,并且进行翻转,从而生成一个新的图像对象。
createImage(Image image, int x, int y, int width, int height, int transform);
用参数source表示源图像,(x,y)分别代表所要截取区域的水平坐标和垂直坐标,width和height分别代表所要截取区域的宽度和高度,transform代表复制过程中的翻转方式。MIDP 2.0定义了9种翻转方法,更详细的内容将在Sprite类章节中进行介绍。
n Sprite.TRANS_NONE:将源图像无翻转地进行复制。
n Sprite.TRANS_ROT90:将源图像顺时针旋转90°后进行复制。
n Sprite.TRANS_ROT180:将源图像顺时针旋转180°后进行复制。
n Sprite.TRANS_ROT270:将源图像顺时针旋转270°后进行复制。
n Sprite.TRANS_MIRROR:将源图像根据垂直中线镜像翻转后进行复制。
n Sprite.TRANS_MIRROR_ROT90:将源图像的镜像顺时针旋转90°后进行复制。
n Sprite.TRANS_MIRROR_ROT180:将源图像的镜像顺时针旋转180°后进行复制。
n Sprite.TRANS_MIRROR_ROT270:将源图像的镜像顺时针旋转270°后进行复制。
例如下面的代码将前面所创建图像顺时针旋转180°后,生成一幅新的图像。
Image trans_image = Image.createImage(image, 0, 0, image.getWidth()0, image.getHeight()0, Sprite.TRANS_ROT180 ); //将源图像进行翻转创建新的图像
将trans_image绘制到屏幕上,如图3-9所示。
3.3.2 图像的锚点
高级用户界面中的不变图像作为不可交互组件,它们的位置由MIDP实现进行控制,通常和其他不可交互的组件放在一行中,按照从左到右的次序排列,并在适当的地方换行。例如,ImageItem中提供了对于图像布局的简单控制,可以插入新行,可以设置左、右对齐和居中对齐。
在低级用户界面中,开发者可以对图像进行像素级的控制。和文本绘制一样,在使用Graphics对象绘制图像时,也提供了锚点的概念,不过图像的锚点和文本的锚点略有不同。
和文本一样,图像也使用限制矩形来确定锚点,不过图像的限制矩形就是它本身所占用的空间,四周不留空隙,因此图像中使用的锚点概念比文本中的简单。
生成一个不变图像后,就可以调用getWidth和getHeight分别返回这个Image对象的宽度和高度,返回值是以像素为单位的整数。例如:
int image_Width = image.getWidth(); //获取图像的宽度
int image_Height = image.getHeight(); //获取图像的高度
如果Image对象是用图像文件生成的,那么返回值image_Width和image_Height就是图3-10中的Width和Height,这样就可以围绕图像确定限制矩形。
为了绘制方便,分别为这个限制矩形制作了垂直中线HCENTER和水平中线VCENTER,把图像的水平方向和垂直方向两等分,这样一共得到3条垂直线:LEFT、HCENTER、RIGHT和3条水平线:TOP、VCENTER、BOTTOM。
这6条线相交得到9个交点,图像的锚点就是这9个点中的一个。锚点用垂直分量和水平分量的OR操作符组合表示。
|
|
图像中没有基线这个概念,因此图像的锚点不出现BASELINE,而字体的锚点没有VCENTER。 |
确定了锚点之后,可以在Canvas中使用Graphics对象的drawImage()方法绘制图像,其定义和drawString()方法的定义类似,如下:
public void drawImage (Image img, int x,int y,int anchor);
参数anchor就是用OR组合起来的锚点常量,参数x和y确定了锚点在坐标系中的位置,参数img就是需要绘制的图像。比如下面的代码,以图像左上方为锚点,并且把锚点定位在Canvas的左上方。
public void paint(Graphics g){
g.setColor(0x00000000); //将画笔颜色设置为黑色
g.fillRect(0, 0, this.getWidth(), this.getHeight()); //用黑色填充屏幕
g.drawImage(image,0,0,g.TOP|g.LEFT); //以图片的左上角作为锚点绘制图片
}
图像的绘制效果如图3-11左图所示,如果把绘制语句改为:
g.drawImage(image,getWidth()/2,getHeight()/2,g.VCENTER|g.HCENTER);
则以图片的中心点为锚点,将图片绘制在屏幕的正中央,其绘制效果如图3-11右图所示。

图3-11 用不同的锚点在不同的位置绘制图像
3.3.3 图像的截取
MIDP还提供了一种特别的绘制方法,使用该方法能够截取图像的任何部分绘制到屏幕上,并且该方法允许对源图像进行翻转和镜像处理。该方法的原型为:
void drawRegion(Image src, int x_src, int y_src, int width, int height, int transform, int x_dest, int y_dest, int anchor);
Image对象src为所要绘制的源图像,x_src,y_src分别为图片区域的左上顶点X坐标和Y坐标,width和height分别为截取区域的宽度和高度,transform 定义图片的旋转和镜像方式,取Sprite里的常量,x_dest和y_dest为绘制目标上的坐标值,anchor为图像锚点。
g.setColor(0x00000000);
g.fillRect(0, 0, this.getWidth(), this.getHeight()); //用黑色填充屏幕
try
{
image = Image.createImage("/tree.png"); //加载图片文件
}catch(java.io.IOException e)
{
System.out.println(e.getMessage());
}
g.drawRegion(image,10,10,80,80,Sprite.TRANS_ROT270,100,50,g.TOP|g.HCENTER);
//对图像进行翻转
编译、运行程序,其结果如图3-12所示。
在Graphics对象中绘制图像不受当前颜色、字体的影响,不过和绘制其他几何图形一样,它受当前Rect区域的影响。
3.3.4 图像的透明
在MIDP 2.0中新增了Alpha混合特性,可以使用这个特性来对图片进行一些处理。首先参考一下MIDP 2.0 java doc中关于Alpha Processing的说明:在可变图像中的每个像素都必须是完全模糊的,在不变图像中的每个像素可以是完全透明的、完全模糊的或者介于两者之间的,也就是半透明。
MIDP实现必须支持存储、处理和绘画全透明和全模糊的像素,当从数据源创建一个图片时(数据源可能来自PNG图片、一个字节数组或者输入流),原图片中的不透明像素和透明像素应该在新图片中保持不变。
对半透明像素的处理就和MIDP实现相关了,要看Alpha混合是否被支持。如果系统实现支持Alpha混合,那么原图中的半透明像素在新图片中依然保持半透明,当然数值可能会根据系统支持的半透明的级别发生一些变化。
如果系统实现不支持Alpha混合,任何半透明的像素在新图片中应该使用全透明的像素来替换。MIDP 2.0新增了一个方法用来创建具有Alpha混合特性的Image对象。
public static Image createRGBImage(int[] rgb,int width,int height,boolean processAlpha)
这个方法允许从rgb数组中创建一个不变图像,rgb数组中的数值形式为0xAARRGGBB,其中AA代表透明度,后面的代表颜色值。在数组中的ARGB数据排列方式为水平方向从左到右,垂直方向从上到下。
如果布尔变量processAlpha为1,那么表示高位的Alpha混和值不可忽略,如果Alpha值为0,则对应的像素就为透明,反之如果Alpha值为255,对应的像素为完全不透明。
如果MIDP实现不支持图像的Alpha混和操作,必须使所有的半透明像素变成全透明像素。如果布尔变量processAlpha为0,那么Alpha值将被忽略,所有的像素都将被视为不透明。
|
|
alpha属性代表了图片的透明度属性,AA代表透明度,0x00代表全透明,0xFF代表完全模糊。 |
根据rgb数组内数据的排列方式,像素点在rgb数组中的定位方法为:P(a, b) = rgb[a + b * width],a、b分别代表象素在图像中的坐标,并且0 <= a < width和0 <= b < height,width和height分别代表图像的宽和高,以像素为单位。
下面的方法同样是Image类新增的,这个方法可以从图片的指定区域读取ARGB像素值,并存储到rgbData数组中,rgbData中的数据是以0xAARRGGBB格式存储的,代表每个像素的颜色属性和透明属性。
public void getRGB(int[] rgbData, int offset, int scanlength, int x, int y,int width,int height);
返回的rgbData不一定和源图像的实际颜色一致,所有的颜色都可能经过重新采样,以适应当前的显示设备的颜色性能,例如,在黑白手机上,所有的红、绿、蓝像素都将被相应的灰度值所替代。
在不支持Alpha通道的设备上,所有的不透明像素的Alpha值都被设成0xFF(全透明),其他象素的Alpha值都被设成0x00。在支持Alpha通道的设备上,Alpha值同样有可能经过重新采样,以适应当前设备所支持的半透明级数。
scanlength定义了数组的扫描宽度,为了避免数据的交叠,其绝对值必须大于或者等于参数width。(x,y)是图像区域的左上角坐标,width和height分别是所截取图像区域的宽度和高度,以像素为单位。
根据rgb数组内数据的排列方式,像素点在rgb数组中的定位方法为:P(a, b)=rgbData[offset + (a - x) + (b - y) * scanlength],其中x <= a < x + width;y <= b < y + height。
所截取的区域不允许超过源图像,这也意味着对参数还有如下限制:x >= 0;y >= 0;x + width <= image width;y + height <= image height。
如果不满足上述条件,程序将抛出IllegalArgumentException异常,如果width <= 0或者height <= 0,不会抛出异常,但也不会有任何像素数据复制到rgbData数组中。rgbData数组和源图像的读取关系如图3-13所示。
Image类提供了两个方法分别来创建ARGB图像和获取图像数据,相应的Graphics也新增了一个drawRGB方法来绘制RGB图像,详细说明请参看Graphics的颜色模型。
游戏中经常会用到半透明效果。但MIDP 1.0年代只有Nokia和LG两家的扩展API给出了可以处理Alpha通道的API。在MIDP 2.0下,可以用Image类提供的方法得到一个图片的半透明版本。例如下面的代码将生成一个半透明的图像:
try
{
image = Image.createImage("/tree.png"); //加载图片文件
}catch(java.io.IOException e)
{
System.out.println(e.getMessage()); //捕获I/O异常
}
int[] argb=new int[image.getWidth()*image.getHeight()];
image.getRGB(argb,0,image.getWidth(),0,0,image.getWidth(),image.getHeight());
//读取图片的ARGB属性
for(int i=0;i<argb.length;i++)
{
argb[i]&=0xa0ffffff; //进行透明处理
}
Image clarity_image = Image.createRGBImage(argb,image.getWidth(),image. getHeight(),true);
g.drawImage(clarity_image,getWidth()/2,getHeight()/2,g.VCENTER|g.HCENTER);
//绘制透明图像
编译、运行程序,其结果如图3-14所示。
3.3.5 用Photoshop制作PNG透明背景
可以为PNG文件设置透明区域,这样在设备屏幕中,透过这些区域可以看到图片的背景,图片预览上的灰白方格图案表示透明区域,如图3-15所示。PNG文件支持Alpha透明,Alpha透明常用在包含渐变透明和半透明像素的导出图形中。
下面简要讲解如何用Photoshop和Fireworks制作透明背景,这两种软件还提供别的功能来优化图片文件,具体细节请参看各自的帮助文档。在Photoshop中若要制作PNG透明背景,可以按照如下步骤进行操作。
(1)打开PNG文件,选择【文件】|【存储为Web文件格式】命令。
(2)将文件格式选择为PNG-8,否则无法选择透明色。
|
|
尽管在“优化”面板中看不到32位PNG的透明度选项,但32位PNG自动包含透明度。 |
(3)在颜色面板中选择一种背景颜色,然后将其设置为透明色,如图3-16所示。

图3-15 图片的背景色不透明和透明 图3-16 颜色面板
(4)在图片预览中如图3-17所示,单击【存储】按钮,显示【优化结果存储为】对话框,设置保存的文件名,保存格式选为【仅限图像】。

图3-17 优化面板
|
|
将颜色设为透明只影响图像的导出版本,而不影响实际图像。可以在预览中查看导出图像的外观。 |
3.3.6 用Fireworks制作PNG透明背景
在Fireworks MX 2004中若要为图像透明选择一种颜色,可以按照以下步骤进行操作。
(1)单击文档窗口左上角的【预览】|【2幅】或【4幅】按钮。在“2幅”或“4幅”视图中,单击除原始视图之外的某个视图。
(2)从优化面板底部的顶部【文件格式】弹出菜单中选择【PNG 8】,在【透明】弹出菜单中选择【索引色透明】,如图3-18所示。
(3)若要选择透明颜色,请单击“选择透明色”按钮。指针变为滴管状。
(4)执行下列操作之一选择要成为透明的颜色。
n 在优化面板颜色表中单击颜色样本。
n 在图片文档中单击一种颜色。
画布颜色在预览中变为透明,如图3-19所示。图形准备好导出。导出时注意保存格式选择【仅图像】。可以观察到,导出的图片大小会比原来的图片大小略有减少。
将透明处理和处理前的图片放置在手机模拟器上比较,如图3-20所示。

图3-19 使用透明功能使图片背景透明 图3-20 在手机上的图片比较






