Android 大图片裁切时遇到的问题

Android 大图片裁切时遇到的问题

写在前边

我有大半年没写过技术文章了,不写的原因主要有三点:

  1. 本人资质有限,写不了纯理论的东西,讨论不了编程语言的优劣,设计不了大型架构;
  2. 那就只能写写各种小问题的解决方案了吧,于是第二个原因来了: 恶心。自从去年一时兴起汇总比较了当时的pdf实现开源方案,收到无数的拿来主义的要求,甚至还收到了无端的指责,我想国内的技术圈应该少一些这类的文章才对,起码能逼着小子们去老老实实看文档;
  3. 懒。

这次主要是因为Android官方文档的不负责,对于这一块讲得过于含糊,一旦碰到了基本上都要吃点苦头;其次是因为经过一番搜索,发现即使是国外也没有太多正确的解决方案。想想看图片处理可是愈发热门的范畴啊,只有冒死垫底了。先说好啊,欢迎邮件与我交流,交流前敬请仔细读完,欢迎指出其中错误。

问题在哪里

Android为了图片裁切提供了很方便的接口,你只用发送这样一个Intent即可:

<!-- lang: java -->
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
intent.setType("image/*");
intent.putExtra("crop", "true");

这个Intent可选参数如下:

一切看上去如此美好,只要在onActivityResult里接收一下系统返回的数据,顺时间成了饭来张口的福贵。
onActivityResult中收到的Intent

<!-- lang: java -->
Intent { act=content://media/external/images/media/1075 (has extras) }

各大论坛上基本上都是下面这个标准答案:

###方案一:return-data设置为true

<!-- lang: java -->
//发送请求
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大小)。怎么回事?

问题一:Intent附带数据大小限制

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存储信息。
如此即可直接从这个存储信息下手,读取最后的结果文件。

<!-- lang: java -->
//发送请求
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包含信息有三大类:

<!-- lang: java -->
// 标准
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携带信息的不靠谱所造成的吃饭不香。

<!-- lang: java -->
//发送请求
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周年校庆。
祝福!

转载于:https://my.oschina.net/ernest/blog/89803

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值