之前的项目中别人做好了头像拍照,选择图片这些操作,没有详细的了解这一块的内容,这几天刚好遇见了这块的东西,前后查找了一些资料,差不多弄明白了些,这里做一个记录。
一般项目中如头像这样的一些图片选择我们都直接调用系统的相机和图片库来操作,所以从这个调用到剪裁一起在捋一捋。
1.如何调用
分析:
Abdroid 系统提供了通过intent 方式访问系统相机和图片库的ACTION,因此我们可以轻松的获取图片。
我们采用Uri的方式返回图片,因此提前写一个Uri。
imageUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "image.jpg"));
选择图片方式:
Intent selectIntent = new Intent(Intent.ACTION_PICK, null);
selectIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(selectIntent, SELECT_IMAGE);//选择图片方式,返回一个data,Uri
//注意:选择图片的结果返回时Uri,在onActivityForResult中获取:Uri uri = data.getData();
拍照方式:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//拍照传一个uri
startActivityForResult(intent, CAMERA_IMAGE);
上述方式将图片选出来或者拍照保存成文件保存在Uri中的路径中。然后我们在onActivityResult()方法中根据请求码进行相应的操作。这里先给出代码。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.e(TAG, "data==" + data);
if (requestCode == CAMERA_IMAGE && resultCode == RESULT_OK) {//拍照
int rotationAngle = getCameraPhotoOrientation(this, imageUri, imageUri.getPath());
Log.e(TAG, "旋转到角度1:" + rotationAngle);
try {
rotateImage(imageUri.getPath());
iv_image.setImageURI(imageUri);
} catch (IOException e) {
e.printStackTrace();
}
int rotationAngle2 = getCameraPhotoOrientation(this, imageUri, imageUri.getPath());
Log.e(TAG, "旋转到角度2:" + rotationAngle2);
} else if (requestCode == SELECT_IMAGE && resultCode == RESULT_OK) {//选择图片工具返回一个uri,不能获取path
Uri uri = data.getData();//此处选择图片返回了uri和物流客一样,公司电脑的uri是自己写的??
Log.e(TAG, "选择图片结果:" + uri);
cropImageUri(uri, 600, 600, CROP_IMAGE);
iv_image.setImageURI(uri);
} else if (requestCode == CROP_IMAGE && resultCode == RESULT_OK && data != null) {
Bitmap bitmap = data.getParcelableExtra("data");
Log.e(TAG, "裁剪图片结果1" + data.getData());
Log.e(TAG, "裁剪图片结果2" + bitmap);//返回结果史bitmap
if (bitmap != null) {
iv_image.setImageBitmap(bitmap);
} else {
iv_image.setImageURI(data.getData());
}
}
}
上述代码分别处理了拍照,选择图片以及图片等裁剪工作,正常情况下这样就可以获取到我们需要的结果了。但是我们在项目中可能遇见其他问题。比如:三星手机拍照上传到服务器后你在获取下来发现图片可能被旋转来90度,拍照的图片太大,上传过程可能失败等。
对于图片出现了旋转,可以在上传前做一个处理,获取到图片旋转角度,然后给他转回来。通过一个方法操作:
/**
* 获取图片的旋转角度
* @param context
* @param imageUri
* @param imagePath
* @return
*/
public static int getCameraPhotoOrientation(Context context, Uri imageUri, String imagePath) {
int rotate = 0;
try {
context.getContentResolver().notifyChange(imageUri, null);
File imageFile = new File(imagePath);
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
switch (orientation) {
case ExifInterface.ORIENTATION_NORMAL:
rotate = 0;
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return rotate;
}
进行旋转:
/**
* @param file
* @throws IOException
*/
public void rotateImage(String file) throws IOException {
BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file, bounds);
BitmapFactory.Options opts = new BitmapFactory.Options();
Bitmap bm = BitmapFactory.decodeFile(file, opts);
File file1 = new File(file);
int rotationAngle = getCameraPhotoOrientation(this, Uri.fromFile(file1), file);
Log.e(TAG, "旋转到角度:" + rotationAngle);
Matrix matrix = new Matrix();
matrix.postRotate(rotationAngle, (float) bm.getWidth() / 2, (float) bm.getHeight() / 2);
Bitmap rotatedBitmap = Bitmap.createBitmap(bm, 0, 0, bounds.outWidth, bounds.outHeight, matrix, true);
FileOutputStream fos = new FileOutputStream(file);
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
}
经过旋转后,上传服务器拿到就不会在被旋转了。
2.图片裁剪压缩
分析:上面完成了从图库获取一个图片和拍照的操作,但是这样拿到的图片一般比较大,尤其是拍照,现在的手机像素都比较大,照片可以达到5M多大小,一般我们没有必要这样大的图片,而且太大了上传也容易失败。有两种操作方式可以对图片进行处理,裁剪和压缩。
裁剪:这种可以用在如头像这样的操作中,裁剪出一个部分来就可以了,裁剪也可以调用系统操作。
这里可能对于调用系统的Intent的参数有不明白,查资料给出一些裁剪Intent的参数:
附加选项 | 数据类型 | 描述 |
crop | String | 发送裁剪信号 |
aspectX | int | X方向上的比例 |
aspectY | int | Y方向上的比例 |
outputX | int | 裁剪区的宽 |
outputY | int | 裁剪区的高 |
scale | boolean | 是否保留比例 |
return-data | boolean | 是否将数据保留在Bitmap中返回 |
data | Parcelable | 相应的Bitmap数据 |
circleCrop | String | 圆形裁剪区域? |
MediaStore.EXTRA_OUTPUT ("output") | URI | 将URI指向相应的file:///...,详见代码示例 |
1.返回一个bitmap
这种形式 主要设置:
intent.putExtra("return-data", true);
//intent.putExtra(MediaStore.EXTRA_OUTPUT, uri_big);//这个情况可以不设置输出的Uri
2.返回一个Uri
设置:
intent.putExtra("return-data", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri_big);
需要设置两个,return-date 为true,并且添加一个uri,最后在onActivityForResult中获取到Uri。其他参数一样
以上设置参数对于从图库选择图片和拍照获取图片时一样的
一种裁剪方式:
/**
* 裁剪图片
*
* @param uri
* @param outputX 输入宽
* @param outputY 输入高
* @param requestCode
*/
private void cropImageUri(Uri uri, int outputX, int outputY, int requestCode) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 2);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", outputX);
intent.putExtra("outputY", outputY);
intent.putExtra("scale", true);
//intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//添加一个输入的uri
intent.putExtra("return-data", true);//裁剪方式:true表示返回一个bitmap,false需要上面的uri
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
Log.e(TAG, "裁剪图片");
startActivityForResult(intent, requestCode);
String s = new String();
}
上述裁剪,输入了一个图片的Uri,设置一个裁剪的宽和高, 最后返回一个Bitmap,我们在onActivityForresult中可以获取到。
Bitmap bitmap = data.getParcelableExtra("data");
如果需要,最后在保存Bitmap为文件上传。
/**
* 把一个bitmap 保存成File
* @param b Bitmap
* @return 图片存储的位置
* @throws
*/
public static String saveImg(Bitmap b,String name) throws Exception{
String path = Environment.getExternalStoragePublicDirectory("longlong").getAbsolutePath();
File mediaFile = new File(path + File.separator + name + ".jpg");
if(mediaFile.exists()){
mediaFile.delete();
}
if(!new File(path).exists()){
new File(path).mkdirs();
}
mediaFile.createNewFile();
FileOutputStream fos = new FileOutputStream(mediaFile);
b.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
b.recycle();
Log.e(TAG,"压缩后的文件:"+mediaFile.getAbsolutePath());
b = null;
System.gc();
return mediaFile.getPath();
}
上述选择图片和裁剪在onActivityForResult中的处理,显示在一个ImageView上
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case SELECT_IMAGE:{//选择图片
if(data.getData()!=null){
cropImageUri(data.getData(),800,800,SHOW_IMAGE);
}
break;
}
case CROP_IMAGE:{//开始裁剪
cropImageUri(uri,800,800,SHOW_IMAGE);
break;
}
case SHOW_IMAGE:{//裁剪后显示图片
if(data!=null ){
Bitmap bitmap = data.getParcelableExtra("data");
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
}else{
String path = data.getData().getPath();
imageView.setImageURI(data.getData());
}
}
break;
}
}
}
结果:(上图选择,下图拍照)
压缩:
方式一:采用比例压缩或者质量压缩
这里采用其中的一个方式:
/**
* 压缩图片保存返回一个路径
* @param oldPath
* @param bitmapMaxWidth
* @return
* @throws Exception
*/
private String getThumbUploadPath(String oldPath,int bitmapMaxWidth) throws Exception {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(oldPath, options);
int height = options.outHeight;
int width = options.outWidth;
int reqHeight = 0;
int reqWidth = bitmapMaxWidth;
reqHeight = (reqWidth * height)/width;
// 在内存中创建bitmap对象,这个对象按照缩放大小创建的
options.inSampleSize = calculateInSampleSize(options, bitmapMaxWidth, reqHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(oldPath, options);
Bitmap bbb = compressImage(Bitmap.createScaledBitmap(bitmap, bitmapMaxWidth, reqHeight, false));
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
.format(new Date());
return saveImg(bbb,timeStamp);
}
方式二:
采用开源压缩实现,这种方式直接使用。
例如:鲁班算法
https://github.com/Curzibn/Luban具体实现:
1.添加依赖:
compile 'io.reactivex:rxandroid:1.2.1' compile 'io.reactivex:rxjava:1.1.6' compile 'top.zibin:Luban:1.0.9'
2.单个文件压缩
Luban.get(this)
.load(File) //传人要压缩的图片
.putGear(Luban.THIRD_GEAR) //设定压缩档次,默认三挡
.setCompressListener(new OnCompressListener() { //设置回调
@Override
public void onStart() {
// TODO 压缩开始前调用,可以在方法内启动 loading UI
}
@Override
public void onSuccess(File file) {
// TODO 压缩成功后调用,返回压缩后的图片文件,这个文件保存在内部的一个缓存文件中,可以直接获取这个文件上传服务器
}
@Override
public void onError(Throwable e) {
// TODO 当压缩过去出现问题时调用
}
}).launch(); //启动压缩
其他关于鲁班的使用方法参考原作者。
其他参考:
https://my.oschina.net/ryanhoo/blog/86843
http://ryanhoo.github.io/blog/2014/06/03/the-ultimate-approach-to-crop-photos-on-android-2/
http://ryanhoo.github.io/blog/2014/06/03/the-ultimate-approach-to-crop-photos-on-android-3/