Android 大图片裁切时遇到的问题
我有大半年没写过技术文章了,不写的原因主要有三点:
这次主要是因为Android官方文档的不负责,对于这一块讲得过于含糊,一旦碰到了基本上都要吃点苦头;其次是因为经过一番搜索,发现即使是国外也没有太多正确的解决方案。想想看图片处理可是愈发热门的范畴啊,只有冒死垫底了。先说好啊,欢迎邮件与我交流,交流前敬请仔细读完,欢迎指出其中错误。
Android为了图片裁切提供了很方便的接口,你只用发送这样一个Intent
即可:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType("image/*");
intent.putExtra("crop", "true");
这个Intent
可选参数如下:
一切看上去如此美好,只要在onActivityResult
里接收一下系统返回的数据,顺时间成了饭来张口的福贵。 onActivityResult
中收到的Intent
:
Intent { act=content://media/external/images/media/1075 (has extras) }
各大论坛上基本上都是下面这个标准答案:
return-data设置为true
//发送请求
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType("image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 64);
intent.putExtra("outputY", 64);
intent.putExtra("scale", true);
intent.putExtra("return-data", true);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, PHOTORESOULT);
//接收数据
if (requestCode == PHOTORESOULT) {
Bundle extras = data.getExtras();
if (extras != null) {
Bitmap photo = extras.getParcelable("data");
...
}
}
拿到可以跑的代码是多么痛快的事情啊,兴冲冲地把output size改成800跑了一番,拿到手的却是个惨不忍睹的缩略图(不完全测试,返回的是640*640大小)。怎么回事?
Android虚拟机为每个应用程序分配的内存大概在16M左右,这在不同厂商出品的修改版中略有不同,比如说国产小米。而Intent本身所能携带的Bitmap数据更小,根据模拟器结果(前文说到机型不同略有不同)大约在1.6M左右。(Android的Bitmap是32位,类型是ARGB_8888。有兴趣可以计算一番。差不多限制在640640左右)注:iOS同样限制在640640,一条心。
总结:接收return-data将直接在extras里附带缩略版的bitmap,适用于头像等小图裁切场景。如果要高清无码之大图呢?
return-data设置为false
返回的Intent
中,action为裁切后产生的图片对应的Uri
,说Uri
有点不大对,上面给出的例子中,action关联的是Android系统级ContentResolver
对应的Media
存储信息。
如此即可直接从这个存储信息下手,读取最后的结果文件。
//发送请求
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType("image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 1000);
intent.putExtra("outputY", 1000);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, PHOTORESOULT);
//接收数据
if (requestCode == PHOTORESOULT) {
if (data != null) {
String action = data.getAction(); //act=content://...
// 处理方式1:直接将media存储地址parse成Uri
Uri uri = Uri.parse(action);
Bitmap bitmap = MediaStore.Images.Media.getBitmap(
this.getContentResolver(), uri);
// 处理方式2:手动处理一下ContentResolver
Cursor cursor = getContentResolver().query(uri, null, null, null,null);
cursor.moveToFirst();
String imgNo = cursor.getString(0); // 图片编号
String imgSize = cursor.getString(2); // 图片大小
String imgName = cursor.getString(3); //图片文件名
String imgPath = cursor.getString(1); // 图片文件路径
...
}
}
Intent
返回信息各大厂商不遵守规定搞分裂Intent
包含信息有三大类:
// 标准
Intent { act=content://media/external/images/media/1075 (has extras) }
// 部分samsung产品,如S3
Intent { (has extras) }
// 部分htc
Intent { act=com.htc.HTCAlbum.action.ITEM_PICKER_FROM_COLLECTIONS (has extras) }
其中最奇葩的是samsung,S3 16G版本和32G版本居然一个标准一个不标准。
分裂是一种痛。
MediaStore.EXTRA_OUTPUT
万幸的是CROP提供的选项中,还有一个,可以自定义裁切输出的图片存储位置。利用这一点,就可以规避Intent
携带信息的不靠谱所造成的吃饭不香。
//发送请求
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType("image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 1000);
intent.putExtra("outputY", 1000);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, getTempUri());
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, PHOTORESOULT);
//选择存储位置
Uri tempPhotoUri;
private Uri getTempUri() {
Uri tempPhotoUri = Uri.fromFile(getTempFile());
return tempPhotoUri;
}
private File getTempFile() {
if (isSDCARDMounted()) {
File f = new File(Environment.getExternalStorageDirectory(),"temp.jpg");
try {
f.createNewFile();
} catch (IOException e) {
...
}
return f;
}
return null;
}
// 注意,在我做的测试中,使用内部缓存是无法正确写入裁切后的图片的。请大家有兴趣的测试一番,看是否有意外。
// 系统是用全局的ContentResolver来做这个过程的文件io操作,app内部的存储被忽略。(猜测)
/*
File f = new File(getCacheDir(), "temp.jpg");
try {
f.createNewFile();
} catch (IOException e) {
...
}
return f;
}
*/
private boolean isSDCARDMounted(){
String status = Environment.getExternalStorageState();
if (status.equals(Environment.MEDIA_MOUNTED)){
return true;
}
return false;
}
// 处理结果
//
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(tempPhotoUri));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Q:直接拍照然后裁切如何处理?
Tips:先调用camera拍照,在onActivityResult里拿到拍到的照片,再发起Crop请求。Crop还有一个设置intent.setDataAndType(「拍到的照片uri」, "image/*");
,可以制定被裁切的图片。建议参考:Android大图片裁剪终极解决方案(下:拍照截图)
crop large photos with android from androidworks
Android大图片裁剪终极解决方案 cc ryanhoo
ryanhoo不辞辛苦提供了可用的代码:PhotoCropper
2012年11月17日是我的母校华南理工大学的60周年校庆。
祝福!