在Android平台中处理图片
处理图像的API有哪些?
Android的API可以实现很多强大的功能,其中包括:
·SQLite结构化数据存储数据库(SQLiteforstructureddatastorage):通过它你不用花很大劲就可以在你的应用程序内嵌入一个微型数据库。
·图形库支持:基于OpenGLES嵌入版的最佳二维图形库和三维图形库。
·集成Web浏览器支持
·多媒体支持:它支持常用的音频、视频和图像格式。
·谷歌API:映射(Mapping)功能可以让第三方代码显示和控制一个谷歌地图。它还可以通过XMPP支持一个叫做GTalkService的P2P服务。
·硬件相关的支持:有很多人们所期待的功能,用来支持GSM电话、蓝牙、3G、WiFi、定位服务等相关硬件。
在谷歌提供的大量API中,我们主要关注下面两个程序包中的相关API:
·android.graphics:核心渲染包,它提供了一些初级图形工具,诸如画布、颜色过滤器、画笔等,可以让你直接在屏幕上进行图像处理。
·android.graphics.drawable:编译过的可视化资源用来做背景、标题或屏幕的其他部分。
由于图片是位图文件,因此我们将重点了解和使用在android.graphics.Bitmap下的API。
文件I/O和支持的图片格式
Android支持好几种常见的静态图片格式,例如PNG、JPG和GIF。在本文的示例中,我们将使用JPG格式。如果你考虑使用图片的透视功能,可能选择PNG格式更合适一些。
为了从你的软件中查看一个图片文件,你应该将它放在你的软件根目录下的res/drawable目录下。一旦这个图片放在这个文件夹下之后,当你重新编译打包的时候,会自动为它产生一个资源ID。举个例子来说,如果你拥有一个叫做pic1.jpg的图片文件,它将可以通过它的资源IDR.drawable.pic1在程序中被访问。你可以看到这个图像文件扩展名已经被脱去,而R则代表了整体资源文件R.java,它是自动生成的,除非你非常了解这个文件中的资源结构,不推荐你编辑它里面的内容。下面的示例代码介绍如何通过一个图像文件的资源ID来访问它。
1BitmapmBitmap=BitmapFactory.decodeResource(getResources(),
2R.drawable.pic1);
3intpic_width=mBitmap.width();
4intpic_height=mBitmap.height();
如果你希望阅读和编写一个没有指定文件夹结构的图片文件,它应该放在模拟器的/data/data/YourPackageName/files/目录下。举个例子来说,如果你为你的例程创建一个程序包名称为com.cyl.TutorialOnImages。那么当你在运行的时候创建一个新的图片文件,它将被放在/data/data/com.cyl.TutorialOnImages/files/文件夹下。请记住每一个Android应用都将以它自己的用户和组ID来启动,因此你专门设定,某些文件夹是不可以通过你的软件来访问的。下面是一段当你希望输出一个位图到一个output.jpg文件时的代码。
1try{
2FileOutputStreamfos=super.openFileOutput("output.jpg",
3MODE_WORLD_READABLE);
4
5mBitmap.compress(CompressFormat.JPEG,75,fos);
6
7fos.flush();
8fos.close();
9}catch(Exceptione){
10Log.e("MyLog",e.toString());
11}
图片查看、颜色和透明
每一个Android应用应该有一个屏幕布局。你可以在软件内动态的创建它,或者通过一个外部XML文件来指定它。这个文件默认是main.xml。为了包含一个图片,你要使用一个叫做ImageView的查看类。下面是main.xml文件的内容:
1<?xmlversion="1.0"encoding="utf-8"?>
2
3<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
4android:orientation="vertical"
5android:layout_width="fill_parent"
6android:layout_height="fill_parent"
7>
8
9<ImageViewid="@+id/picview"
10android:layout_width="wrap_content"
11android:layout_height="wrap_content"
12/>
13
14</LinearLayout>
如同图片文件可以被通过一个资源ID来访问一样,编译后在全局资源文件R.java中自动为main.xml产生一个叫做R.layout.main的资源ID。下面是当应用程序首次在模拟器上启动时的默认视图。
图1、软件在模拟器上首次启动
每一个图像像素通过一个4字节整数来展现。最高位字节用作alpha通道;换言之用来实现透明/不透明控制。255代表完全不透明;0则代表完全透明。接下来一个字节是red红色通道;255代表完全是红色。依次类推接下来两个字节相应的实现绿色和蓝色通道。
操作图像像素
现在你可以对单独的像素进行处理了。通过使用android.graphics.BitmapAPI中的getPixels,可以加载像素到一个整数数组中。在本文例子中,你将按照一定规则对每一个像素实现着色。经过这个处理后,所有的像素将被转化为一个范围在0到255的字节码。android.graphics.BitmapAPI中的setPixels则用来加载这个整数数组到一个图像中。最后一步是通过ImageView变量mIV来更新屏幕。以下是实现这个染色过程的代码片段。
1privatevoidTintThePicture(intdeg){
2int[]pix=newint[picw*pich];
3mBitmap.getPixels(pix,0,picw,0,0,picw,pich);
4
5intRY,GY,BY,RYY,GYY,BYY,R,G,B,Y;
6doubleangle=(3.14159d*(double)deg)/180.0d;
7intS=(int)(256.0d*Math.sin(angle));
8intC=(int)(256.0d*Math.cos(angle));
9
10for(inty=0;y<pich;y++)
11for(intx=0;x<picw;x++)
12{
13intindex=y*picw+x;
14intr=(pix[index]>>16)&0xff;
15intg=(pix[index]>>8)&0xff;
16intb=pix[index]&0xff;
17RY=(70*r-59*g-11*b)/100;
18GY=(-30*r+41*g-11*b)/100;
19BY=(-30*r-59*g+89*b)/100;
20Y=(30*r+59*g+11*b)/100;
21RYY=(S*BY+C*RY)/256;
22BYY=(C*BY-S*RY)/256;
23GYY=(-51*RYY-19*BYY)/100;
24R=Y+RYY;
25R=(R<0)?0:((R>255)?255:R);
26G=Y+GYY;
27G=(G<0)?0:((G>255)?255:G);
28B=Y+BYY;
29B=(B<0)?0:((B>255)?255:B);
30pix[index]=0xff000000|(R<<16)|(G<<8)|B;
31}
32
33Bitmapbm=Bitmap.createBitmap(picw,pich,false);
34bm.setPixels(pix,0,picw,0,0,picw,pich);
35
36//Puttheupdatedbitmapintothemainview
37mIV.setImageBitmap(bm);
38mIV.invalidate();
39
40mBitmap=bm;
41pix=null;
42}
图2:按下中间按钮时的结果
实现用户交互操作
看完上面的内容后,我们已经知道了如何导入和导出一个图像文件,并且为它创建一个视图,进而在这个图片中处理每一个像素。下面我们要了解的一件事情是实现一个简单的用户交互,这样可以根据用户端的输入来实现相应操作。android.view.KeyEvent中的API让你可以处理响应操作。这个软件被设定为等待方向键的中央键的按键事件。当它被按下的时候,它将为这个图片以增加20的方式来进行着色,并将结果保存的一个文件中。
1publicbooleanonKeyDown(intkeyCode,KeyEventevent){
2if(keyCode==KeyEvent.KEYCODE_DPAD_CENTER){
3
4//Performthetintingoperation
5TintThePicture(20);
6
7//Displayashortmessageonscreen
8nm.notifyWithText(56789,"Picturewastinted",
9NotificationManager.LENGTH_SHORT,null);
10
11//Savetheresult
12SaveThePicture();
13
14return(true);
15}
16
17returnsuper.onKeyDown(keyCode,event);
18}
一点建议
·如果处理所有像素要花费比较长的事件,你可能会得到一个应用程序不响应的弹出对话框。这种情况下,你应该创建一个子线程,在它中实现复杂的计算过程。这样可以让主线程实现无中断运行。
·如果这个子线程需要改变主视图(例如对图像进行更新),你应该在主线程中使用一个消息句柄来监听来自子线程的这个消息,并相应的更新视图。一个子线程不能够直接修改主线程中的视图。
·你可以进行某些改进。举个例子来说,它可以被修改类从浏览文件夹或从一个URL中读取图像文件。通过多线程和消息处理可以实现图像动画的处理。