开发案例3——保存指定区域的图片的相机

相信通过前两个案例的开发,大家对于Android开发相机部分已经有了一定的想法,其实起初开始进行Android相机开发的机缘是要做一个想今天要给大家演示的项目,自定义一个相机应用,这个相机应用的目的是能够像微信扫一扫的功能,只不过我当时负责的只是手机客户端这一部分的开发,整个项目还涉及到一些其他领域的算法及技术,在此不做进一步介绍。我们只来讲一讲如何实现保存预览图像其中的一部分图像,其实原理很简单,就是在上一个案例的基础上通过Bitmap类及其相关的方法来实现对图像的裁剪。

还是你们很喜欢的代码!!!

首先我们应该为程序申请相应的权限,所以在AndroidManifest.xml文件中增加如下代码:

<uses-permission android:name="android.permission.CAMERA" />

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-feature android:name="android.hardware.camera" />

<uses-feature android:name="android.hardware.camera.autofocus" />

然后,我们来创建拍照界面布局,这一次的布局相对于上两个案例的界面就要复杂得多,我们通过设置各个组件的透明度来区分有效区域和无效区域,可以看出我们给其中的取景区域加了一个边框,这一部分是通过编写风格化的.xml文件实现的。

/Activity/res/layout/activity_main.xml

代码清单:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:id="@+id/Framelayout01"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    >

    <SurfaceView

       android:id="@+id/sfvCamera"

       android:layout_width="fill_parent"

       android:layout_height="fill_parent">

    </SurfaceView>

 

    <LinearLayout

        android:layout_width="fill_parent"

        android:layout_height="fill_parent"

        android:orientation="vertical">

        <ImageView

            android:id="@+id/ImageView01"

            android:layout_width="fill_parent"

            android:layout_height="120dp"

            android:background="#7f000000"/>

        <View

            android:id="@+id/centerView01"

            android:layout_width="fill_parent"

            android:layout_height="50dp" 

            android:background="@layout/backgroud"     

            />

        <TextView

            android:id="@+id/txt_view"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:text="请让有效图像充满整个取景窗"

            android:gravity="center"

            android:textSize="18dp"

            android:textColor="@android:color/white"

            android:background="#7f000000"/>

        <EditText

            android:id="@+id/txtRearchResult"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:layout_gravity="center"

            android:cursorVisible="false"

            android:textSize="24dp"

            android:textColor="@android:color/white"

            android:background="#7f000000"

            />

         <RelativeLayout

            android:layout_width="fill_parent"

            android:layout_height="fill_parent"

            android:background="#7f000000"

            android:orientation="horizontal">

       

          <TextView

                android:id="@+id/button_recamera"

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:layout_alignParentBottom="true"

                android:text="重拍"

                android:textColor="@android:color/white"

                android:textSize="30dp"/>

 

           <ImageButton

                android:id="@+id/button_camera"

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:layout_alignParentBottom="true"

                android:layout_centerHorizontal="true"

                android:src="@drawable/button"

                android:background="#00000000"/>

 

            <TextView

                android:id="@+id/button_send"

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:layout_alignParentBottom="true"

                android:layout_alignParentRight="true"

                android:text="上传"

                android:textColor="@android:color/white"

                android:textSize="30dp"/>       

       </RelativeLayout>  

    </LinearLayout>

 </FrameLayout>

取景区域加的边框方法是在layout文件夹下新建background.xml文件

/Activity/res/layout/background.xml

代码清单:

<?xml version="1.0" encoding="utf-8"?>

<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 背景色 -->

    <solid android:color="@android:color/transparent"/>

    <!-- 边框色 -->

    <stroke android:width="0.5dip" android:color="#ffffffff"/>

</shape>

最后,依旧是最重要的MainActivity.java文件

/Activity/src/org/williamLee/activity/MainActivity.java

代码清单:

packageorg.williamLee.activity;

 

import java.io.BufferedOutputStream;

importjava.io.ByteArrayOutputStream;

importjava.io.File;

importjava.io.FileInputStream;

import java.io.FileOutputStream;

importjava.io.IOException;

importjava.text.SimpleDateFormat;

importjava.util.Date;

import java.util.Iterator;

importjava.util.List;

 

importorg.ksoap2.SoapEnvelope;

importorg.ksoap2.serialization.SoapObject;

importorg.ksoap2.serialization.SoapSerializationEnvelope;

importorg.ksoap2.transport.HttpTransportSE;

 

importandroid.app.Activity;

importandroid.content.pm.ActivityInfo;

importandroid.content.res.Configuration;

importandroid.graphics.Bitmap;

import android.graphics.BitmapFactory;

importandroid.graphics.Matrix;

importandroid.hardware.Camera;

import android.hardware.Camera.CameraInfo;

importandroid.hardware.Camera.PictureCallback;

importandroid.hardware.Camera.Size;

importandroid.os.Bundle;

importandroid.os.Environment;

importandroid.util.Base64;

importandroid.util.DisplayMetrics;

 

import org.kobjects.base64.*;

importorg.williamLee.activity.R;

 

importandroid.util.Log;

importandroid.view.MotionEvent;

importandroid.view.SurfaceHolder;

import android.view.SurfaceView;

importandroid.view.View;

importandroid.view.View.OnClickListener;

importandroid.view.WindowManager;

import android.widget.Button;

importandroid.widget.EditText;

import android.widget.FrameLayout;

import android.widget.ImageButton;

import android.widget.LinearLayout;

importandroid.widget.TextView;

importandroid.widget.Toast;

 

 

public classMainActivity extends Activity implementsSurfaceHolder.Callback {

    //拍照预览

    private SurfaceView surfaceView;//定义视图控件

    private SurfaceHolder surfaceHolder;//定义控件

    //照相机

    private Camera mCamera;

    //图片地址

    private ImageButton btn_camera;

    private TextView txt_view;

    private TextView btn_recamera;

    private TextView btn_send;

    private View view;

    private EditText edit_txt;

    private byte[] buffer = null;

    private String s;

    private String Url,filename;

    int[] pos=new int[2];

    int surfWidth,surfHeigth;

   

    public voidonCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        //设置布局

        setContentView(R.layout.activity_main);

        //禁止自动锁屏

        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

       

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);

//SurfaceView中获得SurfaceHolder

    surfaceHolder = surfaceView.getHolder();

    surfaceHolder.addCallback(this);

    surfaceHolder.setType(surfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    mCamera = isCameraAvailiable();

   

    if(mCamera==null) Toast.makeText(getApplicationContext(),"相机设备被占用", Toast.LENGTH_SHORT).show();    

    //设置事件监听

    surfaceCreated(surfaceHolder);

    ButtonOnClickListener onClickListener = newButtonOnClickListener();       

    btn_recamera.setOnClickListener(onClickListener);

    btn_camera.setOnClickListener(onClickListener); 

    btn_send.setOnClickListener(onClickListener);

    btn_camera.setVisibility(View.VISIBLE);

    btn_recamera.setVisibility(View.INVISIBLE);

       btn_send.setVisibility(View.INVISIBLE);

    //绑定视图

    surfaceView = (SurfaceView)findViewById(R.id.sfvCamera);

    btn_camera = (ImageButton)findViewById(R.id.button_camera);

    btn_send = (TextView)findViewById(R.id.button_send);

    btn_recamera = (TextView)findViewById(R.id.button_recamera);

    txt_view = (TextView)findViewById(R.id.txt_view);

    view = findViewById(R.id.centerView01);

    edit_txt=(EditText)findViewById(R.id.txtRearchResult);

                                

    //获取整个屏幕大小

    DisplayMetrics dm = newDisplayMetrics();

    getWindowManager().getDefaultDisplay().getMetrics(dm);

    surfWidth=dm.widthPixels;

    surfHeigth=dm.heightPixels;

    }//end of onCreate

       

    //判断照相机设备是否可用

    public static CameraisCameraAvailiable(){

         Camera object = null;

          try {

            object = Camera.open();

         }

          catch(Exception e){    

         }

          return object;

       }

 

 

    class  ButtonOnClickListener implementsOnClickListener{

 

        @Override

        public voidonClick(View v) {

            // TODOAuto-generated method stub

            switch (v.getId()){ 

           case R.id.button_camera: {

            mCamera.takePicture(null, null, capturedIt);

            txt_view.setText("");

            btn_camera.setVisibility(View.INVISIBLE);

            btn_recamera.setText("重拍");

            btn_recamera.setVisibility(View.VISIBLE);

            btn_send.setVisibility(View.VISIBLE);

            break;}

           case R.id.button_recamera:{

            mCamera.startPreview();

            txt_view.setText("请让有效图像充满整个取景窗");

            btn_camera.setVisibility(View.VISIBLE);

            btn_recamera.setVisibility(View.INVISIBLE);

            btn_send.setVisibility(View.INVISIBLE);

            edit_txt.setText("");

            break;

           }

           case R.id.button_send:{       

               saveImageToFile();

            btn_camera.setVisibility(View.INVISIBLE);

//          txt_view.setText("正在解析图片,请稍候···");

            btn_recamera.setText("开始");

            btn_recamera.setVisibility(View.VISIBLE);

            btn_send.setVisibility(View.INVISIBLE);

            s=getImageRecgResult(Url, filename);

            if(s==null){

                Toast.makeText(getApplicationContext(),"请开启网络连接,尝试重拍", Toast.LENGTH_SHORT).show();

                txt_view.setText("网络出错了···");

            }else if(s.length()<=10){

                txt_view.setText("图片质量不满足解析最低要求");

                Toast.makeText(getApplicationContext(),"亲!你可以尝试把玻璃擦干净,重拍识别",Toast.LENGTH_LONG).show();

            }else {

                txt_view.setText("");

                edit_txt.setText(s);

            }

            break;

            }

          }

        }  

       }//end ofclass ButtonOnClickListener

 

    //picture显示

    private PictureCallback capturedIt = newPictureCallback() {

 

        @Override

        public voidonPictureTaken(byte[] data, Camera camera) {

               

       Bitmap bitmap = BitmapFactory.decodeByteArray(data , 0, data .length);

        if(bitmap==null){         

        }

        else

       {  

 

        //设置剪切长宽位置转换

         view.getLocationInWindow(pos);

 

         Matrix m = new Matrix();

            m.setRotate(90);

            Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(),bitmap.getHeight(),m, true);

            

         pos[0]=(int) (pos[0]*(bm.getWidth()/((float)surfWidth)));

            pos[1]=(int) (pos[1]*(bm.getHeight()/((float)surfHeigth)));

            

           Bitmap sbm = Bitmap.createBitmap(bm,pos[0],pos[1],(int) (view.getWidth()*(bm.getWidth()/((float)surfWidth))),(int)(view.getHeight()*(bm.getHeight()/((float)surfHeigth))));

 

            buffer = new byte[Bitmap2Bytes(sbm).length];

            buffer = Bitmap2Bytes(sbm).clone();}        

    }

};//end ofclass PictureCallback

   

    public byte[]Bitmap2Bytes(Bitmap bm) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        bm.compress(Bitmap.CompressFormat.JPEG, 100, baos);

        return baos.toByteArray();

    }//end of B2B

   

    private voidsaveImageToFile(){

         File file = getOutFile();

          if (file == null){

             Toast.makeText(getApplicationContext(), "文件创建失败,请检查SD卡读写权限", Toast.LENGTH_SHORT).show();

             return ;

         }

   

          if (buffer == null){

             Log.i("MyPicture", "自定义相机Buffer: null");

         }else{

             try{

                 FileOutputStream fos = newFileOutputStream(file);

                 fos.write(buffer);

                 fos.flush();

                 fos.close();

             }catch(IOException e){

                 e.printStackTrace();

             }

         }

        }//end ofsaveImageToFile

   

    private File getOutFile() {

        // TODOAuto-generated method stub

          String storageState = Environment.getExternalStorageState();

         if(Environment.MEDIA_REMOVED.equals(storageState)){

            Toast.makeText(getApplicationContext(), "oh,no, SD卡不存在", Toast.LENGTH_SHORT).show();

            return null;

         }

         

        File mediaStorageDir = new File(Environment

                .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)

                ,"MyPictures");

         if (!mediaStorageDir.exists()){

            if (!mediaStorageDir.mkdirs()){

              

mediaStorageDir.getPath());

                return null;

            }

         }

         

        File file = new File(setFilePath(mediaStorageDir));

         

         return file;

    }//end of getOutFile

   

    private String setFilePath(FilemediaStorageDir) {

        // TODOAuto-generated method stub

        String timeStamp =newSimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());

        filename="IMG_"+timeStamp+".jpg";

       String filePath = mediaStorageDir.getPath()+ File.separator;

        Url=filePath;

        filePath += (timeStamp + ".jpg");

      return filePath;

    }//end of getFilePath

       

@Override

       public voidsurfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {

    /************************************************************

     * 目前只考虑到竖屏使用的情况,所以这一部分不用写,当想加入横屏时须在此处配置参数

     */

       }

 

@Override

       public voidsurfaceCreated(SurfaceHolder holder) {

          try   {

         Camera.Parameters parameters = mCamera.getParameters();

         

         if(this.getResources().getConfiguration().orientation!=Configuration.ORIENTATION_LANDSCAPE)

         {            

              parameters.set("orientation", "portrait");

              mCamera.setDisplayOrientation(90);

              parameters.setRotation(0);

         }else {

                 parameters.set("orientation", "landscape");

                 //对于Android 2.2及以上版本

                 mCamera.setDisplayOrientation(0);

                 //对于Android 2.2及以上版本取消注释

                 parameters.setRotation(0);

            }         

            Size largestSize =getBestSupportedSize(parameters.getSupportedPreviewSizes());

            parameters.setPreviewSize(largestSize.width, largestSize.height);// 设置预览图片尺寸 

            parameters.setPictureSize(largestSize.width, largestSize.height);         

            mCamera.setParameters(parameters);

            mCamera.setPreviewDisplay(holder);            

            mCamera.startPreview();

         } catch (IOException e) {

            mCamera.release();

         }

                

       }

 

@Override

       public voidsurfaceDestroyed(SurfaceHolder arg0) {

               mCamera.stopPreview();

               mCamera.release();

   

       }

 

@Override

public booleanonTouchEvent(MotionEvent event) {//屏幕触摸事件

    if (event.getAction()== MotionEvent.ACTION_DOWN) {//按下时自动对焦

        mCamera.autoFocus(null);

        //af =true;

    }

    return true;

}

 

        private SizegetBestSupportedSize(List<Size> sizes) { 

            // 取能适用的最大的SIZE 

            Size largestSize = sizes.get(0); 

            int largestArea = sizes.get(0).height * sizes.get(0).width

            for (Size s : sizes) { 

                int area = s.width * s.height

                if (area > largestArea) { 

                    largestArea = area

                    largestSize = s

                } 

            } 

            return largestSize

        }

 

}

 

以上就是保存预览图像其中的一部分图像的代码实现,下面我们来讲解一下其中的原理。涉及到图片处理部分的代码如下:

 

               

Bitmap bitmap =BitmapFactory.decodeByteArray(data , 0, data .length);

     if(bitmap==null){       

        }

     else

        {//设置剪切长宽位置转换

         view.getLocationInWindow(pos);

 

         Matrix m = new Matrix();

         m.setRotate(90);

 

Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(),bitmap.getHeight(),m, true);

         

pos[0]=(int) (pos[0]*(bm.getWidth()/((float)surfWidth)));

pos[1]=(int) (pos[1]*(bm.getHeight()/((float)surfHeigth)));

          

Bitmap sbm = Bitmap.createBitmap(bm,pos[0],pos[1],

(int) (view.getWidth()*(bm.getWidth()/((float)surfWidth))),

(int)(view.getHeight()*(bm.getHeight()/((float)surfHeigth))));

 

//获取整个屏幕大小

DisplayMetrics dm = newDisplayMetrics();

getWindowManager().getDefaultDisplay().getMetrics(dm);

surfWidth=dm.widthPixels;

surfHeigth=dm.heightPixels;

看到这些新代码你可能瞬间就懵了,没关系我们来解释他的原理:

首先我们要说明一下Android手机屏幕的方向,相机传感器方向以及相机的预览方向;


手机屏幕方向是以屏幕的左上角是坐标系统的原点(0,0)坐标。原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向。

    

                                                                          



物理摄像头采集到的像:手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向,坐标原点位于手机横放时的左上角,即与横屏应用的屏幕X方向一致。所以在编辑竖屏应用时要对其进行旋转90°操作才能看到正逻辑的预览图像。

                                                                             

相机的预览方向:在相机API中可以通过setDisplayOrientation()设置相机预览方向。在默认情况下,这个值为0,与图像传感器一致。因此对于横屏应用来说,由于屏幕方向和预览方向一致,预览图像不会颠倒90度。但是对于竖屏应用,屏幕方向和预览方向垂直,所以会出现颠倒90度现象。为了得到正确的预览画面,必须通过API将相机的预览方向旋转90,保持与屏幕方向一致。

以上是手机预览是三个方向之间的问题,参考于博客http://www.androidchina.net/4381.html

参看以上博文你会对Android相机各种方向有一个清醒的认识,所以再次还是要感谢这篇博文的博主啊!

然后我继续来分享我在接下来的设计时的想法,我发现不同的手机默认存储的照片的方向是不一样的,有的手机会随着手机预览视图的调整来存储,而有的手机却始终存储横着的图像(三星手机),为此,我想到的方法是统一横着存储相片,这样能够照顾到所有的机型。那么,我们现在看到的手机频幕上显示的图片是如图1,而实际存储的照片如图

                                                    

而且,实际存储的照片的尺寸与手机屏幕的尺寸不一样的但比例相等,所以我先将存储的照片通过Bitmap类的相关方法将图片旋转90°,然后按照屏幕尺寸和照片尺寸的比例进行转换,将手机上的有效区域的相关参数(顶点坐标,长,宽)映射到旋转后的照片上从而截取有效的图像存储。

获取屏幕的长和宽

DisplayMetrics dm = newDisplayMetrics();

getWindowManager().getDefaultDisplay().getMetrics(dm);

surfWidth=dm.widthPixels;

surfHeigth=dm.heightPixels;

 

以下代码是实现将图片顺时针旋转90°

Matrix m = new Matrix();

         m.setRotate(90);

 

Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(),bitmap.getHeight(),m, true);

 

将屏幕上View有效图像区域的顶点坐标映射到旋转后的图片上

view.getLocationInWindow(pos);

pos[0]=(int) (pos[0]*(bm.getWidth()/((float)surfWidth)));

pos[1]=(int) (pos[1]*(bm.getHeight()/((float)surfHeigth)));

 

照到图片上的顶点坐标,然后按照映射长宽进行有效图像的截取

Bitmap sbm = Bitmap.createBitmap(bm,pos[0],pos[1],

(int) (view.getWidth()*(bm.getWidth()/((float)surfWidth))),

(int)(view.getHeight()*(bm.getHeight()/((float)surfHeigth))));

 

这样,就实现了截取预览图像中的有效图像进行保存,通过这个原理,我们只需要都相关的界面布局进行设计,就可以实现类似于扫一扫之类的软件了。

 

效果图:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值