拍照、相册及裁剪的终极实现(一)——拍照及裁剪功能实现
来源:互联网 发布:数据库int类型长度 编辑:程序博客网 时间:2024/06/18 16:22
前言:这段时间也真是忙到死,本来一个月四篇的承诺,眼看就要打破了,咬着牙再出两篇,最近写工程时,遇到拍照和裁剪功能,也真是服了,各种问题有木有,最后终于找着了一个解决方案,就目前来讲,应用在工程中,还没有什么问题。如果大家有碰到问题,欢迎留言交流
相关博客:
《拍照、相册及裁剪的终极实现(一)——拍照及裁剪功能实现》
《 拍照、相册及裁剪的终极实现(二)——相册选择及裁剪功能实现》
看来很容易的问题,解决起来却也处处是坑,各种问题啊有木有,要吐血了啊有木有,到网上一搜一堆帖子,能用的基本上没有啊有木有,真是服了……
这里有几篇写的比较好的文章,虽然不是终极方案,但也能我起到了启蒙作用,链接如下:
1、《 如何使用Android MediaStore裁剪大图片》
2、《Android大图片裁剪终极解决方案(上:原理分析)》 及其他文章底部链接中的上、中、下三篇
3、《Android 大图片裁切时遇到的问题》 这篇文章写的极好,分析问题非常出众
4、《Android大图片裁剪终极解决方案 原理分析》这个大哥分析的也极好,不过他的终极解决方案在一些手机上行不通
一、基础讲解
一般而言,使用拍照和裁剪功能基本上都是使用系统自带的Intent来实现,看起来直接调别人的写好的东东会比较容易,但真正用起来确发现根本不是那回事,找不到源码是个最大的问题,话说我至今都没找到源码的位置,有哪位同学知道源码位置的留言下哦。
言规正转,我们要将别人写好的Intent,那肯定要使用隐式Intent的方式来启用了,这里使用的是匹配Action:MediaStore.ACTION_IMAGE_CAPTURE,具体的值是:
(MediaStore.java)
拍照:(MediaStore.ACTION_IMAGE_CAPTURE)
public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
启用相册:(Intent.ACTION_GET_CONTENT)
public static final java.lang.String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT";启用裁剪:
com.android.camera.action.CROP有关启用隐式Intent和显式Intent的方式参看这两篇文章:《 intent详解(一)》、《intent详解(二)》
要使用相应的功能是通过Intent.PutExtra("key",value);来实现的,对应的KEY和Valude
Exta Options Table for image/* crop:
附加选项数据类型描述cropString发送裁剪信号aspectXintX方向上的比例aspectYintY方向上的比例outputXint裁剪区的宽outputYint裁剪区的高scaleboolean是否保留比例return-databoolean是否将数据保留在Bitmap中返回dataParcelable相应的Bitmap数据circleCropString圆形裁剪区域?MediaStore.EXTRA_OUTPUT ("output")URI将URI指向相应的file:///...,详见代码示例outputFormatString输出格式,一般设为Bitmap格式:Bitmap.CompressFormat.JPEG.toString()noFaceDetectionboolean是否取消人脸识别功能这些参数是可以选择性使用的,想使用哪个功能就直接写上,不使用就不写,下面我们就一个个试试。这里我会始终将return_data设为false,因为如何设为TRUE,那对于有些手机而言,只会得到缩略图,所以这里一致用URI来输出。而URI在有些手机上也是存在问题的,这里先不谈,先用URI,因为这是网上一致认为的终极方案………………
写在前面:
由于我们会读写SD卡,所以先在AndroidManifest.xml中添加上SD卡的读写权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
二、仅拍照
(1)、核心代码:
我们仅仅使用拍照的功能,而且将其输出到URI中,代码如下:
启用拍照Activity
private static final int RESULT_CAMERA_ONLY = 100;
先构造一个temp.jpg的URI
String path = getSDCardPath();File file = new File(path + "/temp.jpg");imageUri = Uri.fromFile(file);
然后通过MediaStore.ACTION_IMAGE_CAPTURE来隐式调起拍照Intent;
然后将返回值设为false,并将MediaStore.EXTRA_OUTPU输出指定为 imageUri;
然后将URI的输出格式设为JPEG,这是因为我们在构造URI时,使用的JPG格式:temp.jpg
Intent intent = null; intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra("return-data", false); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); startActivityForResult(intent, RESULT_CAMERA_ONLY);
大家可能对return-data和MediaStore.EXTRA_OUTPUT的作用有些迷糊;
return-data:是将结果保存在data中返回,在onActivityResult中,直接调用intent.getdata()就可以获取值了,这里设为fase,就是不让它保存在data中
MediaStore.EXTRA_OUTPUT:由于我们不让它保存在Intent的data域中,但我们总要有地方来保存我们的图片啊,这个参数就是转移保存地址的,对应Value中保存的URI就是指定的保存地址。至于这两个参数能不能同时设为输出,这个我也不清楚……
在onActivityResult中接收:protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; switch (requestCode) { case RESULT_CAMERA_ONLY: { try { Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri)); mImage.setImageBitmap(bitmap); } catch (Exception e) { e.printStackTrace(); } } break; }}将接收到的ImageUri解析为Bitmap,因为我们的URI本来就是个Bitmap,然后将其设置到一个ImageView中显示;
但在有的手机中ImageView并不会显示这个Bitmap,而查看根目录下的temp.jpg确实已经生成了。这是为什么?
查看日志,报了个错:
Bitmap too large to be uploaded into a texture (3120x4160, max=4096x4096)这是因为Bitmap太大了,超出了ImageView的显示范围所致,这里有三种解决办法:
1、强制显示,关闭OpenGL加速(容易导致OOM)
在AndroidManifest.xml中,application标签下添加一个属性:
android:hardwareAccelerated="false"即:
<application…… android:hardwareAccelerated="false"> </application>2、将图片缩小
将指定图片缩小SCALE倍的代码如下:
但如果图片足够大,缩小后也不定能显示……
private Bitmap setScaleBitmap(Bitmap photo,int SCALE) { if (photo != null) { //为防止原始图片过大导致内存溢出,这里先缩小原图显示,然后释放原始Bitmap占用的内存 //这里缩小了1/2,但图片过大时仍然会出现加载不了,但系统中一个BITMAP最大是在10M左右,我们可以根据BITMAP的大小 //根据当前的比例缩小,即如果当前是15M,那如果定缩小后是6M,那么SCALE= 15/6 Bitmap smallBitmap = zoomBitmap(photo, photo.getWidth() / SCALE, photo.getHeight() / SCALE); //释放原始图片占用的内存,防止out of memory异常发生 photo.recycle(); return smallBitmap; } return null;}
public Bitmap zoomBitmap(Bitmap bitmap, int width, int height) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); Matrix matrix = new Matrix(); float scaleWidth = ((float) width / w); float scaleHeight = ((float) height / h); matrix.postScale(scaleWidth, scaleHeight);// 利用矩阵进行缩放不会造成内存溢出 Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true); return newbmp;}3、分块显示
看这里《解决:Bitmap too large to be uploaded into a texture exception》
(2)、实例
1、AndroidManifest.xml添加SD卡读写权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />2、主布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/btn_camera_only" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="仅拍照"/> <ImageView android:id="@+id/image_result" android:layout_width="200dip" android:layout_height="200dip" android:scaleType="centerInside" android:layout_gravity="center_horizontal"/></LinearLayout>3、主程序(MainActivity.java)
三个变量:
private static final int RESULT_CAMERA_ONLY = 100;private ImageView mImage;private Uri imageUri;其中imageUri用来保存拍照后存储的数据;
OnCreate()函数:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String path = getSDCardPath(); File file = new File(path + "/temp.jpg"); imageUri = Uri.fromFile(file); mImage = (ImageView)findViewById(R.id.image_result); Button btn_take_camera_only = (Button)findViewById(R.id.btn_camera_only); btn_take_camera_only.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takeCameraOnly(); } });}做了两件事:
第一:初始化imageUri
其中的getSDCardPath()是自己写的函数,下面会给出源码;
第二:在点击按钮时调起相机:
private void takeCameraOnly(){ Intent intent = null; intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//action is capture intent.putExtra("return-data", false); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); startActivityForResult(intent, RESULT_CAMERA_ONLY);}然后对返回的数据进行处理:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; switch (requestCode){ case RESULT_CAMERA_ONLY:{ try { Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri)); mImage.setImageBitmap(bitmap); } catch (Exception e) { e.printStackTrace(); } } break; }}其中的获取SD卡路径的代码为:
public static String getSDCardPath() { String cmd = "cat /proc/mounts"; Runtime run = Runtime.getRuntime();// 返回与当前 Java 应用程序相关的运行时对象 try { Process p = run.exec(cmd);// 启动另一个进程来执行命令 BufferedInputStream in = new BufferedInputStream(p.getInputStream()); BufferedReader inBr = new BufferedReader(new InputStreamReader(in)); String lineStr; while ((lineStr = inBr.readLine()) != null) { // 获得命令执行后在控制台的输出信息 if (lineStr.contains("sdcard") && lineStr.contains(".android_secure")) { String[] strArray = lineStr.split(" "); if (strArray != null && strArray.length >= 5) { String result = strArray[1].replace("/.android_secure", ""); return result; } } // 检查命令是否执行失败。 if (p.waitFor() != 0 && p.exitValue() == 1) { // p.exitValue()==0表示正常结束,1:非正常结束 } } inBr.close(); in.close(); } catch (Exception e) { return Environment.getExternalStorageDirectory().getPath(); } return Environment.getExternalStorageDirectory().getPath();}
三、拍照及裁剪初步实现
效果图:
点击按钮弹出拍照Intent,然后进入裁剪Intent,最后将裁剪后的图显示在ImageView中;
(一) (二)
(三) (四)
那我们在上面的纯拍照的代码中加以修改,在点击按钮时,强制拍照Intent,然后结果自己加入裁剪Intent中,结果同样存在imageUri中;
OnCreate()中:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String path = getSDCardPath(); File file = new File(path + "/temp.jpg"); imageUri = Uri.fromFile(file); mImage = (ImageView) findViewById(R.id.image_result); Button btn_take_camera_only = (Button) findViewById(R.id.btn_camera_only); btn_take_camera_only.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takeCameraCropUri(); } }); }开启拍照及裁剪功能:
private void takeCameraCropUri() { Intent intent = null; intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//action is capture 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, imageUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); startActivityForResult(intent, RESULT_CAMERA_CROP_URI); }结果接收:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; switch (requestCode) { case RESULT_CAMERA_CROP_URI: { try { Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri)); //出不来 mImage.setImageBitmap(bitmap); } catch (Exception e) { e.printStackTrace(); } } break; } }一切看起来那么美好,但一运行会发现——拍照完成之后崩了!!!!!!
为什么会这样!!!!我也不知道怎么找源码,只能靠猜了……个人认为,我们把最终结果存在了imageUri中,但从拍照Intent到裁剪Intent之间结果是怎么传的呢?估计是通过Intent中的data来传的,当数据过大,即超过1M时就崩了!!!!
所以我们要想办法分离这个过程,将中间数据先暂存一下,然后再调裁剪Intent,最后把结果存在Uri中。
基于这个想法,下面看看具体实现过程:
四、拍照及裁剪终极方案
首先声明两个Uri,一个保存拍照的结果,一个保存裁剪的结果:
private static final int RESULT_CAMERA_ONLY = 100; private static final int RESULT_CAMERA_CROP_PATH_RESULT = 301; private ImageView mImage; private Uri imageUri; private Uri imageCropUri;然后在OnCreate()函数中初始化:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String path = getSDCardPath(); File file = new File(path + "/temp.jpg"); imageUri = Uri.fromFile(file); File cropFile = new File(getSDCardPath() + "/temp_crop.jpg"); imageCropUri = Uri.fromFile(cropFile); mImage = (ImageView) findViewById(R.id.image_result); Button btn_take_camera_only = (Button) findViewById(R.id.btn_camera_only); btn_take_camera_only.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takeCameraOnly(); } }); }点击按钮时仅调起拍照Intent,将结果存在imageUri中
private void takeCameraOnly() { Intent intent = null; intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//action is capture intent.putExtra("return-data", false); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); startActivityForResult(intent, RESULT_CAMERA_ONLY); }在接收到返回的消息后,调起裁剪Intent:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; switch (requestCode) { case RESULT_CAMERA_ONLY: { cropImg(imageUri); } break; } }其中cropImg(Uri uri)是调起裁剪Intent,代码如下:
public void cropImg(Uri uri) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); intent.putExtra("crop", "true"); intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); intent.putExtra("outputX", 700); intent.putExtra("outputY", 700); intent.putExtra("return-data", false); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageCropUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); startActivityForResult(intent, RESULT_CAMERA_CROP_PATH_RESULT);}将传进去的uri做为源数据,即被裁剪的数据,将结果存储在imageCropUri中;
然后接收返回的结果:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; switch (requestCode) { case RESULT_CAMERA_ONLY: { cropImg(imageUri); } break; case RESULT_CAMERA_CROP_PATH_RESULT: { Bundle extras = data.getExtras(); if (extras != null) { try { Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageCropUri)); mImage.setImageBitmap(bitmap); } catch (Exception e) { e.printStackTrace(); } } } break; } }将存储裁剪结果的imageCropUri,转换为Bitmap,然后在ImageView中显示;
完整的代码如下:
public class MainActivity extends Activity { private static final int RESULT_CAMERA_ONLY = 100; private static final int RESULT_CAMERA_CROP_PATH_RESULT = 301; private ImageView mImage; private Uri imageUri; private Uri imageCropUri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String path = getSDCardPath(); File file = new File(path + "/temp.jpg"); imageUri = Uri.fromFile(file); File cropFile = new File(getSDCardPath() + "/temp_crop.jpg"); imageCropUri = Uri.fromFile(cropFile); mImage = (ImageView) findViewById(R.id.image_result); Button btn_take_camera_only = (Button) findViewById(R.id.btn_camera_only); btn_take_camera_only.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { takeCameraOnly(); } }); } private void takeCameraOnly() { Intent intent = null; intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//action is capture intent.putExtra("return-data", false); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); startActivityForResult(intent, RESULT_CAMERA_ONLY); } public void cropImg(Uri uri) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); intent.putExtra("crop", "true"); intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); intent.putExtra("outputX", 700); intent.putExtra("outputY", 700); intent.putExtra("return-data", false); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageCropUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); intent.putExtra("noFaceDetection", true); startActivityForResult(intent, RESULT_CAMERA_CROP_PATH_RESULT); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; switch (requestCode) { case RESULT_CAMERA_ONLY: { cropImg(imageUri); } break; case RESULT_CAMERA_CROP_PATH_RESULT: { Bundle extras = data.getExtras(); if (extras != null) { try { Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageCropUri)); mImage.setImageBitmap(bitmap); } catch (Exception e) { e.printStackTrace(); } } } break; } } public static String getSDCardPath() { String cmd = "cat /proc/mounts"; Runtime run = Runtime.getRuntime();// 返回与当前 Java 应用程序相关的运行时对象 try { Process p = run.exec(cmd);// 启动另一个进程来执行命令 BufferedInputStream in = new BufferedInputStream(p.getInputStream()); BufferedReader inBr = new BufferedReader(new InputStreamReader(in)); String lineStr; while ((lineStr = inBr.readLine()) != null) { // 获得命令执行后在控制台的输出信息 if (lineStr.contains("sdcard") && lineStr.contains(".android_secure")) { String[] strArray = lineStr.split(" "); if (strArray != null && strArray.length >= 5) { String result = strArray[1].replace("/.android_secure", ""); return result; } } // 检查命令是否执行失败。 if (p.waitFor() != 0 && p.exitValue() == 1) { // p.exitValue()==0表示正常结束,1:非正常结束 } } inBr.close(); in.close(); } catch (Exception e) { return Environment.getExternalStorageDirectory().getPath(); } return Environment.getExternalStorageDirectory().getPath(); }}
源码内容:
1、BlogCameraOnly:仅拍照功能
2、BlogCameraCropCrash:第三部分对应的源码,根本起不来裁剪Intent,造成Crash
3、BlogCameraCropFinally:拍照及裁剪的终极方案;
如果本文有帮到你,记得关注哦
源码下载地址:http://download.csdn.net/detail/harvic880925/8412983
请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/43163175 谢谢。
- 拍照、相册及裁剪的终极实现(一)——拍照及裁剪功能实现
- 拍照、相册及裁剪的终极实现(一)——拍照及裁剪功能实现
- 拍照、相册及裁剪的终极实现(二)——相册选择及裁剪功能实现
- 拍照、相册及裁剪的终极实现(二)——相册选择及裁剪功能实现
- Android 拍照、从相册获取及裁剪的相关实现
- Android拍照及裁剪实现
- 图片拍照或相册选择的实现以及裁剪功能
- 调用系统拍照及裁剪功能主要代码实现
- Android之调用相册、拍照及裁剪功能
- Android之调用相册、拍照及裁剪功能
- Android拍照及相册图片裁剪操作
- SDK4/5/6/7,相册、拍照及裁剪功能及遇见的坑
- 实现相机拍照和相册裁剪
- Android图片操作(拍照,相册选图及裁剪)
- Android开发:相册读取、拍照、图片裁剪和图片上传服务器等功能的实现
- 拍照、相册裁剪
- android头像相册/拍照选取,裁剪及上传综合案例
- android头像相册/拍照选取,裁剪及上传综合案例
- “基数排序”之数组中缺失的数字
- 使用Matrix控制图像或组件变换的步骤
- java5线程框架Executor的用法举例
- angular $resource模块
- “一步千里”之数组找数
- 拍照、相册及裁剪的终极实现(一)——拍照及裁剪功能实现
- C# Managed DirectX 程序基本框架
- 第一章 Hadoop2.x 应用开发step by step——大数据概述
- linux文件特殊权限
- Matrix控制平移、旋转和缩放的方法
- 记录友盟微信登录问题(点击登录后没有反应)
- 墨小七的ios开发学习之路(1)linux命令基础
- 我的webservice Hello world-axis
- 大牛很通俗地介绍《信号与系统》