自定义Camera实现头像框效果,并裁剪指定区域合成

需要一个带框的相机,并且拍好后能合成框和人脸,不过需要人自己凑过去哈哈哈

这两天看了很多博客,然后自己根据自己的要求改了改,基本可以用,调节参数可以获得想要的效果

参考链接在最后面,要是看不懂我的,可以去看看他们的,都挺有参考价值的

效果图:

    

前一张为拍照界面,绿色的是拍照按钮。。。第二章为保存的照片,叠加了背景,中间是裁剪出来的,最上面一层是图片,一共三层。

实现思路:

自定义相机

在surface界面上加一个自定义的view,画上(或者直接覆盖)这张图片,

拍好后,在照片上裁剪出小恐龙图片大小的区域

选好背景,调好裁剪后的照片和小恐龙的位置,叠加照片

注意点:

裁剪时,比例是按照预览窗口中小恐龙控件的比例去裁剪

由于是自定义的相机,所以预览窗口(Preview)大小和照片(Picture)的大小都需要自己设置过。我这里是给了他两个相同的固定值,使其表现的一样。

需要适配的话,可以修改代码中的changePreviewSize方法,在其中设置预览窗口和照片大小,注意,一定要设置成宽高比例差不多的,不然保存的照片会出现拉伸的问题。

相机的自动聚焦,对于不同的机型可能会出现问题,问题的解决参考链接在最后

对于6.0以后,权限的申请我用的是第三方,EasyPermissions,使用方法见参考链接

不BB了,代码

1.在gradle里添加依赖(EasyPermissions)
    implementation 'pub.devrel:easypermissions:1.3.0'

2.加权限

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

3.activity_main.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/myFramelayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/combine_pic3" />

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.example.zhang.diycamerademo01.MyView
            android:id="@+id/myView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

    </FrameLayout>

    <LinearLayout
        android:id="@+id/linear"
        android:layout_width="55dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:layout_marginRight="8dp"
        android:gravity="center_vertical">

        <Button
            android:id="@+id/takepicture_button"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="@drawable/ic_launcher_background"
            android:onClick="takepicture" />

    </LinearLayout>

</RelativeLayout>

4.MyView.java


public class MyView extends View {
    //获取屏幕的宽和高。根据屏幕的宽和高来算框的位置
    private int screenWidth, screenHeight;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setMyParams(int screenWidth, int screenHeight) {
        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;

    }

    @Override
    public void onDraw(Canvas canvas) {
        Bitmap bg1 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();
       //Bitmap toghter = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_toghter1small)).getBitmap();
        //画一个头像框,正中间
        canvas.drawBitmap(bg1,(screenWidth-bg1.getWidth())/2,(screenHeight-bg1.getHeight())/2,null);
        //canvas.drawBitmap(toghter,0,0,null);
        super.onDraw(canvas);
    }
}

public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
    private static final String TAG = "TEST";
    private MyView myView;
    private Camera camera;
    private SurfaceView surfaceview;
    private int screenWidth, screenHeight;
        private ToneGenerator tone;
    private Camera.Parameters parameters;
    //剪切后的图片宽高
    private int retX, retY;
    private float radioX,radioY;
    String[] permissions = new String[]{
            Manifest.permission.CAMERA,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
    };
    // 声明一个集合,在后面的代码中用来存储用户拒绝授权的权
    List<String> mPermissionList = new ArrayList<>();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        //把屏幕设置成横屏
        setContentView(R.layout.activity_main);
        //全屏
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        initRequest();

        WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
        screenWidth = wm.getDefaultDisplay().getWidth();
        screenHeight = wm.getDefaultDisplay().getHeight();

        initScale();

        myView =  findViewById(R.id.myView);
        myView.setMyParams(screenWidth, screenHeight);
        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) myView.getLayoutParams();
        layoutParams.width = screenWidth;
        layoutParams.height = screenHeight;
        myView.setLayoutParams(layoutParams);

        initDIYCamera();

    }
      private Camera.AutoFocusCallback myAutoFocusCallback = new Camera.AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            Log.w("print", "聚焦完成,,,,");
            //聚焦后发出提示音
            tone = new ToneGenerator(AudioManager.STREAM_MUSIC, ToneGenerator.MAX_VOLUME);
            tone.startTone(ToneGenerator.TONE_PROP_BEEP2);
        }
    };

    private void initScale(){
         Bitmap bg3 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();
        int test1=bg3.getHeight();
        int test2=bg3.getWidth();
        radioX=(float) test2/screenWidth;
        radioY=(float) test1/screenHeight;
    }

    private void initDIYCamera() {
        //当第一次进来时,首先执行这个方法,因为没有授权所以找不到相机
        //所以需要再授权后再次找到相机
        myView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                camera.autoFocus(myAutoFocusCallback);
                return false;
            }
        });
        camera = Camera.open(0);
        surfaceview = (SurfaceView) findViewById(R.id.surfaceView);
        SurfaceHolder holder = surfaceview.getHolder();
        holder.addCallback(new MySurfaceCallback());
        holder.setKeepScreenOn(true);// 屏幕常亮 holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
        holder.lockCanvas();
    }

    private void initRequest() {
        //使用easyPermissions
        //先检查是否已经授权
        if (EasyPermissions.hasPermissions(this, permissions)) {

            // 已经申请过权限,做想做的事
        } else {
            // 没有申请过权限,现在去申请,第二个参数为拒绝后再次申请时弹出的文字
            EasyPermissions.requestPermissions(this, "快来申请我哦",
                    1, permissions);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // 把执行结果的操作给EasyPermissions
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    //申请成功时调用
    @Override
    public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
//请求成功执行相应的操作

        //比如,举个例子
        switch (requestCode) {
            case 1:
                //第一次进来,初始化时camera为空
               camera.open(0);
                Toast.makeText(this, "成功获取3个权限", Toast.LENGTH_SHORT).show();
                break;

        }
    }

    @Override
    public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
        //比如,举个例子
        switch (requestCode) {
            case 1:
                Toast.makeText(this, "3个权限未被全部同意", Toast.LENGTH_SHORT).show();
                break;

        }
    }


    private final class MySurfaceCallback implements SurfaceHolder.Callback {
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            //当surface的格式或大小发生改变,这个方法就被调用

        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                if (camera==null){
                    camera = Camera.open(0);
                }
                camera.stopPreview();
                camera.release();
                camera = null;
                camera = Camera.open(0);

                //设置相机预览时大小及图片的大小和camera的参数设置
                changePreviewSize(camera, surfaceview.getWidth(), surfaceview.getHeight());

                camera.setPreviewDisplay(surfaceview.getHolder());
                // 开启预览
                camera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if (camera != null) {
                camera.release();
                camera = null;
            }
        }
    }

    public void takepicture(View v) {
        camera.takePicture(mShutterCallback, null, mPictureCallback);
    }

    Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
        public void onShutter() {
        }
    };

    private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Bitmap mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            Bitmap mBitmapCut = ImageCrop(mBitmap);

            Bitmap bg1 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_pic1)).getBitmap();
            Bitmap bg3 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();

            Bitmap mBitmapFinal = CombineImage(bg1, mBitmapCut, bg3);

            String rootPath = Environment.getExternalStorageDirectory().toString() + File.separator;
            File file = new File(rootPath + System.currentTimeMillis() + ".jpg");
            try {
                file.createNewFile();
                BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
                mBitmapFinal.compress(Bitmap.CompressFormat.JPEG, 100, os);//100 是压缩率,100表示不压缩 os.flush();
                os.close();
                Toast.makeText(getApplicationContext(), "保存成功", Toast.LENGTH_SHORT).show();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };


    /**
     * 修改相机的预览尺寸和保存的相片的尺寸以及camera的属性
     *
     * @param camera     相机实例
     * @param viewWidth  预览的surfaceView的宽
     * @param viewHeight 预览的surfaceView的高
     */
    protected void changePreviewSize(Camera camera, int viewWidth, int viewHeight) {
        if (camera == null) {
            return;
        }
        parameters = camera.getParameters();
        List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
        Camera.Size closelySize = null;//储存最合适的尺寸
        for (Camera.Size size : sizeList) { //先查找preview中是否存在与surfaceview相同宽高的尺寸
            Log.i(TAG, "preview size.width=" + size.width + "  size.height=" + size.height);

            if ((size.width == viewWidth) && (size.height == viewHeight)) {
                closelySize = size;
            }
        }
        if (closelySize == null) {
            // 得到与传入的宽高比最接近的size
            float reqRatio = ((float) viewWidth) / viewHeight;
            float curRatio, deltaRatio;
            float deltaRatioMin = Float.MAX_VALUE;
            for (Camera.Size size : sizeList) {
                if (size.width < 1024) continue;//1024表示可接受的最小尺寸,否则图像会很模糊,可以随意修改
                curRatio = ((float) size.width) / size.height;
                deltaRatio = Math.abs(reqRatio - curRatio);
                if (deltaRatio < deltaRatioMin) {
                    deltaRatioMin = deltaRatio;
                    closelySize = size;
                }
            }
        }

        //设置裁剪后全屏高清的代码
        // 获取摄像头支持的PictureSize列表
        List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
        List<Camera.Size> middlePicSizeList = new ArrayList<>();//储存比例最接近且与预览窗口最接近大小的尺寸
        Camera.Size closelyPicSize = null;//储存最合适的照片尺寸
        for (Camera.Size size : pictureSizeList) { //先查找Pic中是否存在与surfaceview相同宽高的尺寸
            Log.i(TAG, "pictureSizeList size.width=" + size.width + "  size.height=" + size.height);
            if ((size.width == viewWidth) && (size.height == viewHeight)) {
                closelyPicSize = size;
            }
        }
        if (closelyPicSize == null) {
            // 得到与传入的宽高比最接近的PicSize
            float reqRatio = ((float) viewWidth) / viewHeight;
            float curRatio, deltaRatio;
            float deltaRatioMin = Float.MAX_VALUE;
            for (Camera.Size size : pictureSizeList) {
                if (size.width < 1024) continue;//1024表示可接受的最小尺寸,否则图像会很模糊,可以随意修改
                curRatio = ((float) size.width) / size.height;
                deltaRatio = Math.abs(reqRatio - curRatio);
                if (deltaRatio < deltaRatioMin) {
                    deltaRatioMin = deltaRatio;
                    middlePicSizeList.add(size);
                }
            }
            for (Camera.Size size:middlePicSizeList){
                float middleSize;
                Log.i(TAG, "pictureMiddleList size.width=" + size.width + "  size.height=" + size.height);

                float middleSizeMin = Float.MAX_VALUE;
                middleSize = Math.abs(size.width - closelySize.width);
                if (middleSize<middleSizeMin){
                    middleSizeMin = middleSize;
                    closelyPicSize=size;
                }
            }
        }

        if (closelySize != null) {
            Log.i("changePreviewSize", "预览尺寸修改为:" + closelySize.width + "*" + closelySize.height);
            //设置大小
            //parameters.setPreviewSize(closelySize.width, closelySize.height);
            //由于裁剪是依据preview界面中MyView的比例来算的,所以preview界面和保存的照片的
            // 宽高比例要一致,才不会导致拉伸
            parameters.setPreviewSize(1920, 1080);
            //设置图片的大小,同样可以和preview一样修改,只是现在把他弄成了固定数值
            parameters.setPictureSize(1920, 1080);
            //设置自动对焦
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            camera.setParameters(parameters);
        }
    }

    /**
     * 按图片比例裁切图片
     */
    public Bitmap ImageCrop(Bitmap bitmap) {
        // 得到图片的宽,高
        int w = bitmap.getWidth();
        int h = bitmap.getHeight();

        //剪切的初始xy位置,原点位置都是根据preview
        //界面的控件比例来算的
        int x = (int) ((w-(w * radioX ))/2);
        int y = (int) ((h-(h * radioY )) /2);
        //剪切后的宽和高
        retX = (int) (w * radioX);
        retY = (int) (h * radioY);

        Bitmap afterCrop = Bitmap.createBitmap(bitmap, x, y, retX, retY, null, false);
        return afterCrop;
    }

    /*
    叠加图片
     */
    public Bitmap CombineImage(Bitmap b1, Bitmap b2, Bitmap b3) {
        //创建一张大小和背景图一致的位图
        int bgWidth = b1.getWidth();
        int bgHeight = b1.getHeight();
        int fgWidth = b2.getWidth(); //前景图宽度,用于后面计算前景图的绘制坐标
        int fgHeight = b2.getHeight(); //前景图高度,用于后面计算前景图的绘制坐标
        Bitmap newbmp = Bitmap.createBitmap(bgWidth, bgHeight, Bitmap.Config.RGB_565);

        //缩放照片
        int beforeX=b3.getWidth();
        int beforeY=b3.getHeight();
        float scaleWidth = ((float) retX) / beforeX;
        float scaleHeight = ((float) retY) / beforeY;
        // 取得想要缩放的matrix參數
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);//两个参数未缩放的比例
        Bitmap afterCrop = Bitmap.createBitmap(b3, 0, 0, beforeX, beforeY, matrix, true);
        
//新建画布
        Canvas cv = new Canvas(newbmp);
//在0,0坐标开始画入bg
        cv.drawBitmap(b1, 0, 0, null);
//开始画入fg,可以从任意位置画入,具体位置自己计算f
        cv.drawBitmap(b2, (bgWidth - fgWidth) / 2, (bgHeight - fgHeight) / 2, null);
//开始画入第三层,可以从任意位置画入,具体位置自己计算f
        cv.drawBitmap(afterCrop, (bgWidth - fgWidth) / 2, (bgHeight - fgHeight) / 2, null);
        return newbmp;
    }
}

 

总结:

这个demo还是很不完整的,很多的细节,比如位置等,都是需要自己看着调的,自己通过改一些东西也能实现大部分功能。

加油!!!哈哈哈

 

demo下载:https://download.csdn.net/download/jiye111/10626903

 

参考链接:

自定义相机: https://blog.csdn.net/ming_csdn_/article/details/70154381

自动对焦:https://blog.csdn.net/yanzi1225627/article/details/8577682

图片叠加功能:https://www.jb51.net/article/138971.htm

EasyPermissions使用方法:https://blog.csdn.net/u011138190/article/details/79776902

Fail to connect to camera service的几种原因和解决方法:https://blog.csdn.net/wang_shuai_ww/article/details/44560113

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值