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开发笔记的完整目录

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券