4.8 使用位图操作类Bitmap
类Bitmap的完整写法是Android.Graphics.Bitmap,此类能够对位图实现基本操作。类Bitmap的功能最复杂,其中最为常用的是如下8个方法。
· boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream):压缩一个Bitmap对象,根据相关的编码、画质保存到一个OutputStream中。其中第1个压缩格式目前有JPG和PNG;
· void copyPixelsFromBuffer(Buffer src):从一个Buffer缓冲区复制位图像素;
· void copyPixelsToBuffer(Buffer dst):将当前位图像素内容复制到一个Buffer缓冲区;
· final int getHeight():获取高度;
· final int getWidth():获取宽度;
· final boolean hasAlpha():是否有透明通道;
· void setPixel(int x, int y, int color):设置某像素的颜色;
· int getPixel(int x, int y):获取某像素的颜色。
4.8.1 Bitmap类的功能
1. 从资源中获取位图
可以使用BitmapDrawable或者BitmapFactory来获取资源中的位图。首先需要获取资源:
Resources res=getResources();
(1)使用BitmapDrawable获取位图的基本流程如下。
step 1 使用BitmapDrawable (InputStream is)构造一个BitmapDrawable。
step 2 使用BitmapDrawable类的getBitmap()获取得到位图。
例如,通过下面的代码读取InputStream并得到位图:
InputStream is=res.openRawResource(R.drawable.pic180); BitmapDrawable bmpDraw=new BitmapDrawable(is); Bitmap bmp=bmpDraw.getBitmap();
也可以采用下面的方式:
BitmapDrawable bmpDraw=(BitmapDrawable)res.getDrawable(R.drawable.pic180); Bitmap bmp=bmpDraw.getBitmap();
(2)使用BitmapFactory获取位图。
使用BitmapFactory类decodeStream(InputStream is)解码位图资源,然后获取位图。
Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic180);
BitmapFactory的所有函数都是静态的,这个辅助类可以通过资源ID、路径、文件、数据流等方式来获取位图。
以上方法在编程的时候读者可以自由选择,在Android SDK说明中可以支持的图片格式如下:png(preferred)、jpg(acceptable)、gif(discouraged)和bmp(Android SDK Support Media Format)。
2. 获取位图的信息
要获取位图信息,如获取位图大小、像素、密度、透明度、颜色格式等,得到Bitmap就迎刃而解了,这些信息在Bitmap的手册中,这里需要说明以下两点:
(1)在Bitmap中对RGB颜色格式使用Bitmap.Config定义,仅包括ALPHA_8、ARGB_4444、ARGB_8888、RGB_565,缺少了一些其他元素,如RGB_555,在开发中可能需要注意这个小问题;
(2)Bitmap还提供了compress()接口来压缩图片,不过Android SDK只支持PNG、JPG格式的压缩,其他格式的需要Android开发人员自己补充。
3. 显示位图
可以使用核心类Canvas来显示位图,通过Canvas类的drawBitmap()显示位图,或者借助于BitmapDrawable来将Bitmap绘制到Canvas。当然,也可以通过BitmapDrawable将位图显示到View中。
(1)转换为BitmapDrawable对象显示位图,代码示例如下。
// 获取位图 Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic180); // 转换为BitmapDrawable对象 BitmapDrawable bmpDraw=new BitmapDrawable(bmp); // 显示位图 ImageView iv2 = (ImageView)findViewById(R.id.ImageView02); iv2.setImageDrawable(bmpDraw);
(2)使用Canvas类显示位图。
在此可以采用一个继承自View的子类Panel,在子类的OnDraw中显示,具体代码如下所示。
public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new Panel(this)); } class Panel extends View{ public Panel(Context context) { super(context); } public void onDraw(Canvas canvas){ Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.pic180); canvas.drawColor(Color.BLACK); canvas.drawBitmap(bmp, 10, 10, null); } } }
4.8.2 Bitmap应用实例
实例4-6 使用Bitmap类模拟水纹效果(daima\4\BitmapCH1)。
本实例是Bitmap类的成名实例,实例文件BitmapCH1.java的主要代码如下所示。
public class BitmapCH1 extends View implements Runnable { int BACKWIDTH; int BACKHEIGHT; short[] buf2; short[] buf1; int[] Bitmap2; int[] Bitmap1; public BitmapCH1(Context context) { super(context); /* 装载图片 */ Bitmap image = BitmapFactory.decodeResource(this.getResources(),R.drawable.qq); BACKWIDTH = image.getWidth(); BACKHEIGHT = image.getHeight(); buf2 = new short[BACKWIDTH * BACKHEIGHT]; buf1 = new short[BACKWIDTH * BACKHEIGHT]; Bitmap2 = new int[BACKWIDTH * BACKHEIGHT]; Bitmap1 = new int[BACKWIDTH * BACKHEIGHT]; /* 加载图片的像素到数组中 */ image.getPixels(Bitmap1, 0, BACKWIDTH, 0, 0, BACKWIDTH, BACKHEIGHT); new Thread(this).start(); } void DropStone(int x,// x坐标 int y,// y坐标 int stonesize,// 波源半径 int stoneweight)// 波源能量 { for (int posx = x - stonesize; posx < x + stonesize; posx++) for (int posy = y - stonesize; posy < y + stonesize; posy++) if ((posx - x) * (posx - x) + (posy - y) * (posy - y) < stonesize * stonesize) buf1[BACKWIDTH * posy + posx] = (short) -stoneweight; } void RippleSpread() { for (int i = BACKWIDTH; i < BACKWIDTH * BACKHEIGHT - BACKWIDTH; i++) { // 波能扩散 buf2[i]=(short)(((buf1[i-1]+buf1[i+1]+buf1[i-BACKWIDTH]+buf1[i+ BACKWIDTH]) >> 1) - buf2[i]); // 波能衰减 buf2[i] -= buf2[i] >> 5; } // 交换波能数据缓冲区 short[] ptmp = buf1; buf1 = buf2; buf2 = ptmp; } /* 渲染水纹效果 */ void render() { int xoff, yoff; int k = BACKWIDTH; for (int i = 1; i < BACKHEIGHT - 1; i++) { for (int j = 0; j < BACKWIDTH; j++) { //计算偏移量 xoff = buf1[k - 1] - buf1[k + 1]; yoff = buf1[k - BACKWIDTH] - buf1[k + BACKWIDTH]; //判断坐标是否在窗口范围内 if ((i + yoff) < 0) { k++; continue; } if ((i + yoff) > BACKHEIGHT) { k++; continue; } if ((j + xoff) < 0) { k++; continue; } if ((j + xoff) > BACKWIDTH) { k++; continue; } //计算出偏移像素和原始像素的内存地址偏移量 int pos1, pos2; pos1 = BACKWIDTH * (i + yoff) + (j + xoff); pos2 = BACKWIDTH * i + j; Bitmap2[pos2++] = Bitmap1[pos1++]; k++; } } } public void onDraw(Canvas canvas) { super.onDraw(canvas); /* 绘制经过处理的图片效果 */ canvas.drawBitmap(Bitmap2, 0, BACKWIDTH, 0, 0, BACKWIDTH, BACKHEIGHT, false, null); } //触笔事件 public boolean onTouchEvent(MotionEvent event) { return true; } //按键按下事件 public boolean onKeyDown(int keyCode, KeyEvent event) { return true; } //按键弹起事件 public boolean onKeyUp(int keyCode, KeyEvent event) { DropStone(BACKWIDTH/2, BACKHEIGHT/2, 10, 30); return false; } public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return true; } /*线程处理*/ public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } RippleSpread(); render(); //使用postInvalidate可以直接在线程中更新界面 postInvalidate(); } } }
执行后将通过对图像像素的操作来模拟水纹效果,如图4-6所示。
图4-6 执行效果
实例 4-7 使用Bitmap类旋转一幅图片(daima\4\BitmapCH2)。
本实例是影响Bitmap类一生的实例,实现流程如下所示。
step 1 编写布局文件main.xml,实现整体布局,主要代码如下。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:background="@drawable/white" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/myTextView1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/app_name"/> <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" > <Button android:id="@+id/myButton1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_button1" /> <ImageView android:id="@+id/myImageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> <Button android:id="@+id/myButton2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_button2" /> </LinearLayout> </LinearLayout>
step 2 编写处理文件BitmapCH2.java,分别实现左旋转按钮事件mButton1.setOnClickListener和右旋转按钮事件mButton2.setOnClickListener。文件BitmapCH2.java的主要实现代码如下所示。
public class BitmapCH2 extends Activity { private Button mButton1; private Button mButton2; private TextView mTextView1; private ImageView mImageView1; private int ScaleTimes; private int ScaleAngle; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mButton1 =(Button) findViewById(R.id.myButton1); mButton2 =(Button) findViewById(R.id.myButton2); mTextView1 = (TextView) findViewById(R.id.myTextView1); mImageView1 = (ImageView) findViewById(R.id.myImageView1); ScaleTimes = 1; ScaleAngle = 1; final Bitmap mySourceBmp = BitmapFactory.decodeResource(getResources(), R.drawable.hippo); final int widthOrig = mySourceBmp.getWidth(); final int heightOrig = mySourceBmp.getHeight(); /* 程序刚运行,加载默认的Drawable */ mImageView1.setImageBitmap(mySourceBmp); /* 向左旋转按钮 */ mButton1.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub ScaleAngle--; if(ScaleAngle<-5) { ScaleAngle = -5; } /* ScaleTimes=1,维持1:1的宽高比例*/ int newWidth = widthOrig * ScaleTimes; int newHeight = heightOrig * ScaleTimes; float scaleWidth = ((float) newWidth) / widthOrig; float scaleHeight = ((float) newHeight) / heightOrig; Matrix matrix = new Matrix(); /* 使用Matrix.postScale设置维度 */ matrix.postScale(scaleWidth, scaleHeight); /* 使用Matrix.postRotate方法旋转Bitmap*/ //matrix.postRotate(5*ScaleAngle); matrix.setRotate(5*ScaleAngle); /* 创建新的Bitmap对象 */ Bitmap resizedBitmap = Bitmap.createBitmap (mySourceBmp, 0, 0, widthOrig, heightOrig, matrix, true); BitmapDrawable myNewBitmapDrawable = new BitmapDrawable(resizedBitmap); mImageView1.setImageDrawable(myNewBitmapDrawable); mTextView1.setText(Integer.toString(5*ScaleAngle)); } }); /* 向右旋转按钮 */ mButton2.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { ScaleAngle++; if(ScaleAngle>5) { ScaleAngle = 5; } /* ScaleTimes=1,维持1:1的宽高比例*/ int newWidth = widthOrig * ScaleTimes; int newHeight = heightOrig * ScaleTimes; /* 计算旋转的Matrix比例 */ float scaleWidth = ((float) newWidth) / widthOrig; float scaleHeight = ((float) newHeight) / heightOrig; Matrix matrix = new Matrix(); /* 使用Matrix.postScale设置维度 */ matrix.postScale(scaleWidth, scaleHeight); /* 使用Matrix.postRotate方法旋转Bitmap*/ //matrix.postRotate(5*ScaleAngle); matrix.setRotate(5*ScaleAngle); /* 创建新的Bitmap对象 */ Bitmap resizedBitmap = Bitmap.createBitmap (mySourceBmp, 0, 0, widthOrig, heightOrig, matrix, true); BitmapDrawable myNewBitmapDrawable = new BitmapDrawable(resizedBitmap); mImageView1.setImageDrawable(myNewBitmapDrawable); mTextView1.setText(Integer.toString(5*ScaleAngle)); } }); } }
执行后将显示一幅图片和两个按钮,单击“左转”和“右转”按钮,会实现对图片的旋转处理,如图4-7所示。
图4-7 执行效果