Android开发札记(九十四)图片的基本加工
Android开发笔记(九十四)图片的基本加工
compress : 根据设定的位图格式与压缩质量,对图片进行压缩。
recycle : 回收位图对象资源。
createBitmap : 从源图片中裁剪一块位图区域。
createScaledBitmap : 根据设定的目标大小,对源图片进行缩放。
getByteCount : 获取位图的字节大小。
getWidth : 获取位图的宽度。
getHeight : 获取位图的高度。
至于Bitmap与Drawable之间的转换,则是通过BitmapDrawable来完成,其中Bitmap转Drawable的代码例子如下:
下面是调用系统服务实现图片裁剪的代码例子(只列出了关键代码):
自己编码实现裁剪图片的话,一般是把图片分为两块区域,一块是裁剪的内部区域,需高亮显示;另一块位于裁剪区域外部,需阴影显示。这个编码似乎没有捷径,博主想到的办法是采用FrameLayout布局,内部放三个子视图,分别是:
1、原图片的ImageView;
2、阴影部分的View,裁剪开始时显示,裁剪结束后隐藏;
3、裁剪区域的ImageView,裁剪开始时显示,裁剪结束后隐藏;
这里实现的难点在于裁剪区域的ImageView,得基于ImageView自定义一种视图CropImageView。该视图的编码思路大致有三部分内容,首先,我们要按照设定的区域从原图片中截取一块位图出来,该功能可调用Bitmap的createBitmap方法来实现。其次,在手势按下时,根据当前按下的位置,判断接下来的裁剪动作,是拖动整个裁剪区域,还是移动某条边,还是移动某个角,这里一共要做十个判断(四条边、四个角、按在区域内部要拖动、按在区域外部不处理)。最后,重写onTouchEvent方法,在按下动作ACTION_DOWN时初始化触摸条件,在移动操作ACTION_MOVE时,根据裁剪动作刷新图片显示。
下面是自定义裁剪视图的效果截图:

下面是CropImageView的实现代码例子:
点此查看Android开发笔记的完整目录
位图管理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开发笔记的完整目录