IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]Android实战技巧之四十七:不用预览拍照与图片缩放剪裁

    lincyang发表于 2015-11-16 23:05:35
    love 0

    副标题: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



沪ICP备19023445号-2号
友情链接