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 执行效果