Android实战技巧之四十七:不用预览拍照与图片缩放剪裁
2015-11-16 23:05
651 查看
副标题:Take Picture without preview Android
Google出于对隐私的保护,制定了一条门槛,即在Android应用开发中编写拍照程序是必需要有图像预览的。这会对那些恶意程序比如Android中泛滥的Service在后台偷偷记录手机用户的行为与周边信息。这样的门槛还包括手机厂商自带的相机软件在拍照时必须是有声音,这样要避免一些偷拍的情况。处于技术调研与一些特殊无害场景的使用,我们要用到不用预览的拍照。此文就是以此为背景,做的一些调研。只是用不多与五款手机测试,不保证所有的设备都无问题。
免责声明:本文纯属技术研究,请勿用于有害场景!
方案
既然Google在技术上做出了特定要求,网友们想出各种workaround的方法,比如用一个空的SurfaceView或将此preview设成透明。但这两个办法我都失败了。而网友Sam给出的将preview设置成1x1大小的方案起了作用。下面的demo就是演示此方案:
界面甚是简单,一个按钮用以拍照,onClick事件方法名为onTakePhotoClicked。
public void onTakePhotoClicked(View view) { final SurfaceView preview = new SurfaceView(this); SurfaceHolder holder = preview.getHolder(); // deprecated setting, but required on Android versions prior to 3.0 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); holder.addCallback(new SurfaceHolder.Callback() { @Override //The preview must happen at or after this point or takePicture fails public void surfaceCreated(SurfaceHolder holder) { Log.d(TAG, "Surface created"); Camera camera = null; try { camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); Log.d(TAG, "Opened camera"); try { camera.setPreviewDisplay(holder); } catch (IOException e) { throw new RuntimeException(e); } camera.startPreview(); Log.d(TAG, "Started preview"); camera.takePicture(null, null, pictureCallback); } catch (Exception e) { if (camera != null) camera.release(); throw new RuntimeException(e); } } @Override public void surfaceDestroyed(SurfaceHolder holder) {} @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} }); WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams( 1, 1, //Must be at least 1x1 WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, 0, //Don't know if this is a safe default PixelFormat.UNKNOWN); //Don't set the preview visibility to GONE or INVISIBLE wm.addView(preview, params); }
图片缩放与裁剪
现在几乎就可以了,pictureCallback是拍照成功后的回调,我们将在此回调中做一些对图片数据的处理工作。首先,图片数据以字节数组的形式返回的,原型如下:
@Override public void onPictureTaken(byte[] data, Camera camera) {}
我们要将数组转换成bitmap,用BitmapFactory.decodeByteArray方法即可。
但你会发现,图片是向左倒着显示的,不要急,我们要把它正过来。此时我们要用matrix.postRotate方法配上Bitmap.createBitmap创建新的bitmap。
谈谈缩放吧,参考Bitmap.createScaledBitmap方法即可。
那么裁剪呢?新建一个Rect,注意它要在整个bitmap中,比如程序中我将进行距离原图1/4处裁剪。
这还没完,做完这些工作,我会将这三个bitmap存成三张图片。注意在做bitmap的compress操作时,第二个参数quality很重要,图片的质量越高,占用的空间越大。
好了,下面是代码,请参考:
private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { Log.d(TAG, "onPictureTaken"); if(null == data){ return; } Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); camera.stopPreview(); Matrix matrix = new Matrix(); matrix.postRotate((float) 90.0); bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); Log.d(TAG, "original bitmap width: " + bitmap.getWidth() + " height: " + bitmap.getHeight()); Bitmap sizeBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/3, bitmap.getHeight()/3, true); Log.d(TAG,"size bitmap width "+sizeBitmap.getWidth()+" height "+sizeBitmap.getHeight()); //裁剪bitmap int leftOffset = (int)(sizeBitmap.getWidth() * 0.25); int topOffset = (int)(sizeBitmap.getHeight() * 0.25); Rect rect = new Rect(leftOffset, topOffset, sizeBitmap.getWidth() - leftOffset, sizeBitmap.getHeight() - topOffset); Bitmap rectBitmap = Bitmap.createBitmap(sizeBitmap, rect.left, rect.top, rect.width(), rect.height()); try { FileOutputStream outputStream = new FileOutputStream(Environment .getExternalStorageDirectory().toString()+"/photoResize.jpg"); sizeBitmap.compress(Bitmap.CompressFormat.JPEG, 30, outputStream); outputStream.close(); FileOutputStream outputStreamOriginal = new FileOutputStream(Environment .getExternalStorageDirectory().toString()+"/photoOriginal.jpg"); bitmap.compress(Bitmap.CompressFormat.JPEG, 20, outputStreamOriginal); outputStreamOriginal.close(); FileOutputStream outputStreamCut = new FileOutputStream(Environment .getExternalStorageDirectory().toString()+"/photoCut.jpg"); rectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStreamCut); outputStreamCut.close(); Log.d(TAG,"picture saved!"); } catch(Exception e) { e.printStackTrace(); } } };
你需要的权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
参考:
http://stackoverflow.com/questions/2386025/taking-picture-from-camera-without-preview
相关文章推荐
- android webview javascript api扩展, armeabi-v7 armeabi,兼谈SDK及其开发者的品行
- Android屏幕适配全攻略(最权威的官方适配指导)
- 安卓位置服务简介
- android 之 下拉刷新PullToRefresh
- Android系统常用隐藏命令大全
- Android 开源库StickyListHeadersListView来实现ListView列表分组效果
- Android 开源库StickyListHeadersListView来实现ListView列表分组效果
- android EditText组件
- Android_自定义水波纹菜单弹出效果
- android基础--AsyncTask
- 探讨Android中Activity的生命周期和加载模式
- Android裁剪图片为圆形
- 使用ListView时的几个BUG与相应的优化
- Android平板上开发应用的一点心得——精确适配不同的dpi和屏幕尺寸
- Android 6.0 gradle 打release包 混淆问题
- Android 语音合成(使用科大讯飞sdk)
- android控件之Menu
- Android Studio非常方便的快捷键(更新中)
- 菜鸟于飞------从嵌入式软件开发到android 应用开发的思考
- Android Spinner选择同一个选项时触发onItemSelected事件