位图管理Bitmap
Android上的图形使用Drawable类,而位图管理则使用Bitmap类,java上与之对应的是awt包中的BufferedImage。Android开发中有需要对jpg、png文件进行加工的,都是操作Bitmap,下面是Bitmap类的常用方法说明:
compress : 根据设定的位图格式与压缩质量,对图片进行压缩。
recycle : 回收位图对象资源。
createBitmap : 从源图片中裁剪一块位图区域。
createScaledBitmap : 根据设定的目标大小,对源图片进行缩放。
getByteCount : 获取位图的字节大小。
getWidth : 获取位图的宽度。
getHeight : 获取位图的高度。
图片读写
图片文件的读写,其实就是Bitmap对象与图片文件的转换操作,有关图片文件读写的说明参见《 Android开发笔记(三十三)文本文件和图片文件的读写》,下面是图片文件读写的示例代码:
public static void saveBitmap(String path, Bitmap bitmap) {
try {
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(path));
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos);
bos.flush();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Bitmap openBitmap(String path) {
Bitmap bitmap = null;
try {
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(path));
bitmap = BitmapFactory.decodeStream(bis);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
至于Bitmap与Drawable之间的转换,则是通过BitmapDrawable来完成,其中Bitmap转Drawable的代码例子如下:
Drawable mDrawable = new BitmapDrawable(getResources(), bitmap);
Drawable转Bitmap的代码例子如下:
Bitmap bitmap = ((BitmapDrawable)mDrawable).getBitmap();
图片加工
常用的图片加工操作有:图片压缩、调整大小、图片裁剪、图片旋转等等,其中图片旋转的介绍参见《 Android开发笔记(九十九)圆形转盘》。下面是图片压缩、调整大小、图片裁剪的使用说明:
图片压缩
压缩算法主要有两个因素,一个是图片格式,另一个是压缩质量,图片压缩可调用compress方法来实现。下面是图片压缩的代码例子:
import java.util.Locale;
import java.util.Map;
import com.example.exmimage.dialog.FileSaveFragment;
import com.example.exmimage.dialog.FileSaveFragment.FileSaveCallbacks;
import com.example.exmimage.dialog.FileSelectFragment;
import com.example.exmimage.dialog.FileSelectFragment.FileSelectCallbacks;
import com.example.exmimage.util.BitmapUtil;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
public class CompressActivity extends Activity implements OnClickListener
,OnLongClickListener,FileSelectCallbacks,FileSaveCallbacks {
private ImageView iv_compress;
private EditText et_quality;
private Drawable mDrawable = null;
private String mExtesion;
private int mQuality;
private String[] extensionArray = {"jpg", "png"};
class ExtensionSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
mExtesion = extensionArray[arg2];
}
public void onNothingSelected(AdapterView<?> arg0) {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_compress);
Button btn_open_drawable = (Button) findViewById(R.id.btn_open_drawable);
Button btn_save_drawable = (Button) findViewById(R.id.btn_save_drawable);
btn_open_drawable.setOnClickListener(this);
btn_save_drawable.setOnClickListener(this);
iv_compress = (ImageView) findViewById(R.id.iv_compress);
et_quality = (EditText) findViewById(R.id.et_quality);
et_quality.setOnLongClickListener(this);
ArrayAdapter<String> extensionAdapter = new ArrayAdapter<String>(this,
R.layout.spinner_item, extensionArray);
extensionAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
Spinner sp = (Spinner) findViewById(R.id.sp_format);
sp.setPrompt("请选择图片格式");
sp.setAdapter(extensionAdapter);
sp.setOnItemSelectedListener(new ExtensionSelectedListener());
sp.setSelection(0);
}
@Override
public boolean onLongClick(View v) {
if (v.getId() == R.id.et_quality) {
et_quality.setText("");
}
return true;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_open_drawable) {
FileSelectFragment.show(this, new String[]{"jpg","png"}, null);
} else if (v.getId() == R.id.btn_save_drawable) {
if (mDrawable == null) {
Toast.makeText(this, "请先打开图片文件", Toast.LENGTH_LONG).show();
return;
}
mQuality = Integer.parseInt(et_quality.getText().toString());
if (mQuality>100 || mQuality<10) {
Toast.makeText(this, "图片质量有效值为10-100", Toast.LENGTH_LONG).show();
return;
}
FileSaveFragment.show(this, mExtesion);
}
}
@Override
public boolean onCanSave(String absolutePath, String fileName) {
return true;
}
@Override
public void onConfirmSave(String absolutePath, String fileName) {
String path = String.format("%s/%s", absolutePath, fileName);
Bitmap bitmap = ((BitmapDrawable)mDrawable).getBitmap();
BitmapUtil.saveBitmap(path, bitmap, mExtesion, mQuality);
Toast.makeText(this, "成功保存图片文件:"+path, Toast.LENGTH_LONG).show();
}
@Override
public void onConfirmSelect(String absolutePath, String fileName,
Map<String, Object> map_param) {
String extension = fileName.substring(fileName.lastIndexOf(".")+1);
if (extension.toUpperCase(Locale.getDefault()).equals("PNG") == true) {
mExtesion = "png";
} else {
mExtesion = "jpg";
}
String path = String.format("%s/%s", absolutePath, fileName);
Bitmap bitmap = BitmapUtil.openBitmap(path);
mDrawable = new BitmapDrawable(getResources(), bitmap);
iv_compress.setImageDrawable(mDrawable);
//这里也可以直接使用setImageBitmap
//iv_compress.setImageBitmap(bitmap);
//但是setBackground就只能用Drawable,不能用Bitmap了
//iv_compress.setBackground(mDrawable);
}
@Override
public boolean isFileValid(String absolutePath, String fileName,
Map<String, Object> map_param) {
return true;
}
}
调整大小
调整图片大小可使用createScaledBitmap方法,该函数保留了图片的全貌,只做尺寸的缩小和放大。下面是调整图片大小的代码例子:
import java.util.Locale;
import java.util.Map;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.exmimage.dialog.FileSaveFragment;
import com.example.exmimage.dialog.FileSelectFragment;
import com.example.exmimage.dialog.FileSaveFragment.FileSaveCallbacks;
import com.example.exmimage.dialog.FileSelectFragment.FileSelectCallbacks;
import com.example.exmimage.util.BitmapUtil;
public class ResizeActivity extends Activity implements OnClickListener,
OnLongClickListener, FileSelectCallbacks, FileSaveCallbacks {
private ImageView iv_resize;
private TextView tv_width, tv_height;
private EditText et_width, et_height;
private int mWidth, mHeight;
private int mNewWidth, mNewHeight;
private Bitmap mBitmap = null;
private String mExtesion;
private int mQuality = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_resize);
Button btn_open_resize = (Button) findViewById(R.id.btn_open_resize);
Button btn_save_resize = (Button) findViewById(R.id.btn_save_resize);
btn_open_resize.setOnClickListener(this);
btn_save_resize.setOnClickListener(this);
iv_resize = (ImageView) findViewById(R.id.iv_resize);
tv_width = (TextView) findViewById(R.id.tv_width);
tv_height = (TextView) findViewById(R.id.tv_height);
et_width = (EditText) findViewById(R.id.et_width);
et_width.setOnLongClickListener(this);
et_width.addTextChangedListener(mWidthWatcher);
et_height = (EditText) findViewById(R.id.et_height);
et_height.setOnLongClickListener(this);
et_height.addTextChangedListener(mHeightWatcher);
}
@Override
public boolean onLongClick(View v) {
if (v.getId() == R.id.et_width) {
et_width.setText("");
} else if (v.getId() == R.id.et_height) {
et_height.setText("");
}
return true;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_open_resize) {
FileSelectFragment.show(this, new String[] { "jpg", "png" }, null);
} else if (v.getId() == R.id.btn_save_resize) {
if (mBitmap == null) {
Toast.makeText(this, "请先打开图片文件", Toast.LENGTH_LONG).show();
return;
}
mNewWidth = Integer.parseInt(et_width.getText().toString());
mNewHeight = Integer.parseInt(et_height.getText().toString());
if (mNewWidth<=0 || mNewHeight<=0) {
Toast.makeText(this, "新图片的宽和高必须大于0", Toast.LENGTH_LONG)
.show();
return;
}
FileSaveFragment.show(this, mExtesion);
}
}
@Override
public boolean onCanSave(String absolutePath, String fileName) {
return true;
}
@Override
public void onConfirmSave(String absolutePath, String fileName) {
String path = String.format("%s/%s", absolutePath, fileName);
Bitmap bitmap = Bitmap.createScaledBitmap(mBitmap, mNewWidth, mNewHeight, false);
BitmapUtil.saveBitmap(path, bitmap, mExtesion, mQuality);
Toast.makeText(this, "成功保存图片文件:" + path, Toast.LENGTH_LONG).show();
}
@Override
public void onConfirmSelect(String absolutePath, String fileName,
Map<String, Object> map_param) {
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
if (extension.toUpperCase(Locale.getDefault()).equals("PNG") == true) {
mExtesion = "png";
} else {
mExtesion = "jpg";
}
String path = String.format("%s/%s", absolutePath, fileName);
mBitmap = BitmapUtil.openBitmap(path);
iv_resize.setImageBitmap(mBitmap);
mWidth = mBitmap.getWidth();
mHeight = mBitmap.getHeight();
tv_width.setText(""+mWidth);
tv_height.setText(""+mHeight);
}
@Override
public boolean isFileValid(String absolutePath, String fileName,
Map<String, Object> map_param) {
return true;
}
private TextWatcher mWidthWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
float newWidth = Integer.parseInt(s.toString());
et_height.removeTextChangedListener(mHeightWatcher);
et_height.setText(""+(int)(newWidth/mWidth*mHeight));
et_height.addTextChangedListener(mHeightWatcher);
}
};
private TextWatcher mHeightWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
float newHeight = Integer.parseInt(s.toString());
et_width.removeTextChangedListener(mWidthWatcher);
et_width.setText(""+(int)(newHeight/mHeight*mWidth));
et_width.addTextChangedListener(mWidthWatcher);
}
};
}
图片裁剪
裁剪图片有两种方法,一种是调用系统服务com.android.camera.action.CROP,该方法编码简单,但功能有限;另一种是自己写个裁剪算法,编码麻烦些,不过可定制实现复杂的功能。
下面是调用系统服务实现图片裁剪的代码例子(只列出了关键代码):
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_open_system) {
FileSelectFragment.show(this, new String[] { "jpg", "png" }, null);
} else if (v.getId() == R.id.btn_save_system) {
if (mNewBitmap == null) {
Toast.makeText(this, "请先打开并裁剪图片文件", Toast.LENGTH_LONG).show();
return;
}
FileSaveFragment.show(this, mExtesion);
} else if (v.getId() == R.id.btn_cut_system) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(Uri.parse("file://"+mOldFilePath), "image/*");
// 设置裁剪
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// outputX outputY 是裁剪图片宽高
intent.putExtra("outputX", 300);
intent.putExtra("outputY", 300);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse("file://"+mNewFilePath));
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
startActivityForResult(intent, RESULT_REQUEST_CODE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (requestCode == RESULT_REQUEST_CODE) {
mNewBitmap = BitmapUtil.openBitmap(mNewFilePath);
iv_system_new.setImageBitmap(mNewBitmap);
//iv_system_new.setImageDrawable(Drawable.createFromPath(mNewFilePath));
}
}
}
自己编码实现裁剪图片的话,一般是把图片分为两块区域,一块是裁剪的内部区域,需高亮显示;另一块位于裁剪区域外部,需阴影显示。这个编码似乎没有捷径,博主想到的办法是采用FrameLayout布局,内部放三个子视图,分别是:
1、原图片的ImageView;
2、阴影部分的View,裁剪开始时显示,裁剪结束后隐藏;
3、裁剪区域的ImageView,裁剪开始时显示,裁剪结束后隐藏;
这里实现的难点在于裁剪区域的ImageView,得基于ImageView自定义一种视图CropImageView。该视图的编码思路大致有三部分内容,首先,我们要按照设定的区域从原图片中截取一块位图出来,该功能可调用Bitmap的createBitmap方法来实现。其次,在手势按下时,根据当前按下的位置,判断接下来的裁剪动作,是拖动整个裁剪区域,还是移动某条边,还是移动某个角,这里一共要做十个判断(四条边、四个角、按在区域内部要拖动、按在区域外部不处理)。最后,重写onTouchEvent方法,在按下动作ACTION_DOWN时初始化触摸条件,在移动操作ACTION_MOVE时,根据裁剪动作刷新图片显示。
下面是自定义裁剪视图的效果截图:
下面是CropImageView的实现代码例子:
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
public class CropImageView extends ImageView {
public CropImageView(Context context) {
super(context);
}
public CropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private Bitmap mOrigBitmap = null;
private Bitmap mCropBitmap = null;
private Rect mRect = new Rect(0,0,0,0);
public void setOrigBitmap(Bitmap orig) {
mOrigBitmap = orig;
}
public Bitmap getOrigBitmap() {
return mOrigBitmap;
}
public Bitmap getCropBitmap() {
return mCropBitmap;
}
public boolean setBitmapRect(Rect rect) {
if (mOrigBitmap == null) {
return false;
}
if (rect.left<0 || rect.left>mOrigBitmap.getWidth()) {
return false;
}
if (rect.top<0 || rect.top>mOrigBitmap.getHeight()) {
return false;
}
if (rect.right<=0 || rect.left+rect.right>mOrigBitmap.getWidth()) {
return false;
}
if (rect.bottom<=0 || rect.top+rect.bottom>mOrigBitmap.getHeight()) {
return false;
}
mRect = rect;
setPadding(mRect.left, mRect.top, 0, 0);
mCropBitmap = Bitmap.createBitmap(mOrigBitmap,
mRect.left, mRect.top, mRect.right, mRect.bottom);
setImageBitmap(mCropBitmap);
postInvalidate();
return true;
}
public Rect getBitmapRect() {
return mRect;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mOriginX = event.getX();
mOriginY = event.getY();
mOriginRect = mRect;
mDragMode = getDragMode(mOriginX, mOriginY);
break;
case MotionEvent.ACTION_MOVE:
int offsetX = (int) (event.getX()-mOriginX);
int offsetY = (int) (event.getY()-mOriginY);
Rect rect = null;
if (mDragMode == DRAG_NONE) {
return true;
} else if (mDragMode == DRAG_WHOLE) {
rect = new Rect(mOriginRect.left+offsetX, mOriginRect.top+offsetY, mOriginRect.right, mOriginRect.bottom);
} else if (mDragMode == DRAG_LEFT) {
rect = new Rect(mOriginRect.left+offsetX, mOriginRect.top, mOriginRect.right-offsetX, mOriginRect.bottom);
} else if (mDragMode == DRAG_RIGHT) {
rect = new Rect(mOriginRect.left, mOriginRect.top, mOriginRect.right+offsetX, mOriginRect.bottom);
} else if (mDragMode == DRAG_TOP) {
rect = new Rect(mOriginRect.left, mOriginRect.top+offsetY, mOriginRect.right, mOriginRect.bottom-offsetY);
} else if (mDragMode == DRAG_BOTTOM) {
rect = new Rect(mOriginRect.left, mOriginRect.top, mOriginRect.right, mOriginRect.bottom+offsetY);
} else if (mDragMode == DRAG_LEFT_TOP) {
rect = new Rect(mOriginRect.left+offsetX, mOriginRect.top+offsetY, mOriginRect.right-offsetX, mOriginRect.bottom-offsetY);
} else if (mDragMode == DRAG_RIGHT_TOP) {
rect = new Rect(mOriginRect.left, mOriginRect.top+offsetY, mOriginRect.right+offsetX, mOriginRect.bottom-offsetY);
} else if (mDragMode == DRAG_LEFT_BOTTOM) {
rect = new Rect(mOriginRect.left+offsetX, mOriginRect.top, mOriginRect.right-offsetX, mOriginRect.bottom+offsetY);
} else if (mDragMode == DRAG_RIGHT_BOTTOM) {
rect = new Rect(mOriginRect.left, mOriginRect.top, mOriginRect.right+offsetX, mOriginRect.bottom+offsetY);
}
setBitmapRect(rect);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}
private int DRAG_NONE = 0;
private int DRAG_WHOLE = 1;
private int DRAG_LEFT = 2;
private int DRAG_RIGHT = 3;
private int DRAG_TOP = 4;
private int DRAG_BOTTOM = 5;
private int DRAG_LEFT_TOP = 6;
private int DRAG_RIGHT_TOP = 7;
private int DRAG_LEFT_BOTTOM = 8;
private int DRAG_RIGHT_BOTTOM = 9;
private int mDragMode = DRAG_NONE;
private int mInterval = 10;
private float mOriginX, mOriginY;
private Rect mOriginRect;
private int getDragMode(float f, float g) {
int left = mRect.left;
int top = mRect.top;
int right = mRect.left + mRect.right;
int bottom = mRect.top + mRect.bottom;
if (Math.abs(f-left)<=mInterval && Math.abs(g-top)<=mInterval) {
return DRAG_LEFT_TOP;
} else if (Math.abs(f-right)<=mInterval && Math.abs(g-top)<=mInterval) {
return DRAG_RIGHT_TOP;
} else if (Math.abs(f-left)<=mInterval && Math.abs(g-bottom)<=mInterval) {
return DRAG_LEFT_BOTTOM;
} else if (Math.abs(f-right)<=mInterval && Math.abs(g-bottom)<=mInterval) {
return DRAG_RIGHT_BOTTOM;
} else if (Math.abs(f-left)<=mInterval && g>top+mInterval && g<bottom-mInterval) {
return DRAG_LEFT;
} else if (Math.abs(f-right)<=mInterval && g>top+mInterval && g<bottom-mInterval) {
return DRAG_RIGHT;
} else if (Math.abs(f-left)<=mInterval && g>top+mInterval && g<bottom-mInterval) {
return DRAG_LEFT;
} else if (Math.abs(g-top)<=mInterval && f>left+mInterval && f<right-mInterval) {
return DRAG_TOP;
} else if (Math.abs(g-bottom)<=mInterval && f>left+mInterval && f<right-mInterval) {
return DRAG_BOTTOM;
} else if (f>left+mInterval && f<right-mInterval
&& g>top+mInterval && g<bottom-mInterval) {
return DRAG_WHOLE;
} else {
return DRAG_NONE;
}
}
}
点此查看Android开发笔记的完整目录